// C documentation generator using markdeep.
// - rlyeh, public domain
//
// build:
// cl docs.c && docs ..\..\v4k.h -x=3rd_glad.h,v4k.h,v4k_main.h,v4k_compat.h, > docs.html
//
// how to document:
// - start documentation on a blank line with a /// 3 slashes comment.
// - all comments must be typed right before the symbol declaration.
// - use `see: symbol1,symbol2^` to create links. `^` char indicates an external link.
// - use `keyword: comment` to create tables. `return:` and `returns` are reserved keywords for function returns.
// - use `> code` to create a snippet. snippets will be multi-lined.
// - use `!!! text` to create a note. severity based on number of exclamations (1:note,2:tip,3:warn,4:alert)
//
// exceptions:
// - a ///- comment can be used to exclude a symbol from any further documentation processing.
// - this ///- comment must be appended to any declaration, at the very end of string.
//
//          #define my_macro() macro_implementation_here() ///-
//
// some examples below:
//
//   /// !!! `filename` must contain extension
//   /// load dynamic library `file` and search for `symbol`
//   /// return: NULL if not found, found symbol otherwise.
//   /// filename: path to dynamic library file. must contain extension.
//   /// symbol: symbol name. must not be NULL.
//   /// see: dlopen^, dlclose^
//   /// > bool (*plugin_init)(void) = dll("plugin.dll", "init");
//   /// > assert(plugin_init());
//   API void* dll(const char *filename, const char *symbol);
//
//   /// split `string` after any of `delimiters` character is found.
//   /// returns temporary array of split strings. see: strjoin
//   /// > array(char*) tokens = strsplit("hello! world!", " !"); // [0]="hello",[1]="world",
//   API array(char*) strsplit(const char *string, const char *delimiters);
//
//   /// concatenate all elements within `list`, with `separator` string in between.
//   /// returns: temporary joint string. see: strsplit
//   /// > array(char*) tokens = strsplit("hello! world!", " !"); // [0]="hello",[1]="world",
//   /// > char *joint = strjoin(tokens, "+"); // joint="hello+world"
//   API char*        strjoin(array(char*) list, const char *separator);
//
//   /// flags when constructing the image_t type. see: image, image_from_mem
//   /// IMAGE_R: 1-channel image (R)
//   /// IMAGE_RG: 2-channel image (R,G)
//   /// IMAGE_RGB: 3-channel image (R,G,B)
//   /// IMAGE_RGBA: 4-channel image (R,G,B,A)
//   /// IMAGE_FLIP: Flip image vertically
//   enum IMAGE_FLAGS {
//       IMAGE_R    = 0x01000,
//       IMAGE_RG   = 0x02000,
//       IMAGE_RGB  = 0x04000,
//       IMAGE_RGBA = 0x08000,
//       IMAGE_FLIP = 0x10000,
//   };
//
//   /// type that holds linear uncompressed bitmap of any given dimensions.
//   /// w,h: image dimensions in pixels. `x,y` alias.
//   /// comps: number of components per pixel. `n` alias.
//   /// pixels: untyped pointer to linear bitmap data. typed pointers use `pixels8/16/32/f` aliases.
//   /// see: texture_t
//   typedef struct image_t {
//       union { unsigned x, w; };
//       union { unsigned y, h; };
//       union { unsigned n, comps; };
//       union { void *pixels; uint8_t *pixels8; uint16_t *pixels16; uint32_t *pixels32; float *pixelsf; };
//   } image_t;

#include "v4k.h"
#include <ctype.h>

int DO_CSS = 1;
int DO_DEBUG = 0;
int DO_README = 1;
int DO_CHANGELOG = 1;

// C preprocessor:
// 1. remove all /* C comments */
// 2. remove all // C++ comments
// 3. remove all \macro continuation lines
// 4. preserve /// comments
void preprocess_c(char *raw) {
    for(int i = 0, in_string = 0; raw[i]; ++i) {
        if( !in_string ) {
            if( raw[i] == '\"' || raw[i] == '\'' ) if( raw[i-1] != '\\' ) {
                in_string = raw[i];
                continue;
            }

            // preserve ///comments
            if( memcmp(&raw[i], "///", 3) == 0 ) {
                while( raw[i] && raw[i] >= 32 ) ++i;
                --i; continue;
            }

            // remove C++ comments
            if( raw[i] == '/' && raw[i+1] == '/' && raw[i+2] != '/' ) {
                do raw[i++] = ' '; while( raw[i] && (raw[i] >= 32 || raw[i] == '\t'));
                --i; continue;
            }

            // remove \macro continuation lines
            if(raw[i] == '\\' && (raw[i+1] && raw[i+1] < 32) && (raw[i+2] && raw[i+2] < 32)) raw[i]=raw[i+1]=raw[i+2] = ' ';
            if(raw[i] == '\\' && (raw[i+1] && raw[i+1] < 32)) raw[i]=raw[i+1] = ' ';

            // remove /* C comments */
            if(raw[i] == '/' && raw[i+1] == '*') {
                raw[i]=raw[i+1]=' ';
                while(raw[i]) if(raw[i] == '*' && raw[i+1] == '/') break; else raw[i++] = ' ';
                if (raw[i]) raw[i] = ' ';
                if (raw[i + 1]) raw[i + 1] = ' ';
                --i; continue;
            }

        } else {
            while(raw[i] == '\\' && raw[i+1]) i += 2;
            if(raw[i] == in_string ) in_string = 0;
            //if(DO_DEBUG) raw[i] = toupper(raw[i]);
        }
    }
    // remove all linefeeds within {} scopes (flatten scopes into single-lines)
    for(int i = 0, open = 0, in_string = 0; raw[i]; ++i) {
        if( !in_string ) {
            if( raw[i] == '\"' || raw[i] == '\'' ) if( raw[i-1] != '\\' ) {
                in_string = raw[i];
                continue;
            }
            // inline brackets. do not process extern "C" { ... } brackets
            if(raw[i] == '{') if(i >= 12 && !memcmp("extern \"C\" {", &raw[i-11], 12) ? 0 : 1) ++open;
            if(raw[i] == '}') if(open > 0) --open;
            if(open > 0) if(raw[i] && raw[i] < 32) raw[i] = ' ';
        } else {
            while(raw[i] == '\\' && raw[i+1]) i += 2;
            if(raw[i] == in_string ) in_string = 0;
        }
    }
}

// Markdown preprocessor:
// 1. replace all {{TEMPLATES}} if defined in OS environment && small enough for inlining
char* preprocess_md(char *raw) {
    for(int i = 0, in_string = 0; raw[i]; ++i) {
        if( !in_string ) {
            if( raw[i] == '\"' /* || raw[i] == '\'' */ ) if( raw[i-1] != '\\' ) {
                in_string = raw[i];
                continue;
            }

            // replace {{TEMPLATE}} symbols if they exist and fit in-place
            if( raw[i] == '{' && raw[i+1] == '{' && raw[i+2] >= 'A') {
                char buf[128];
                if( sscanf(raw+i+2, "%127[^}]", buf) == 1 ) {
                    int buflen = strlen(buf);
                    char *eos = &raw[i+2] + buflen;
                    if( buflen && eos[0] == '}' && eos[1] == '}' ) {
                        char *repl = getenv(buf);
                        if( repl && strlen(repl) <= (buflen + 4) ) {
                            memset(raw+i, ' ', buflen + 4);
                            memcpy(raw+i, repl, strlen(repl));
                            i += buflen+4;
                            --i; continue;
                        }
                    }
                }
            }

        } else {
            while(raw[i] == '\\' && raw[i+1]) i += 2;
            if(raw[i] == in_string ) in_string = 0;
            //if(DO_DEBUG) raw[i] = toupper(raw[i]);
        }
    }
    return raw;
}

// remove whitespaces within a line, unless they are present within a \"string\"
char *minify( const char *line ) {
    // skip initial whitespaces
    line += strspn(line, " \t\r\n");

    // reserve space
    char *bak = strdup/*STRDUP*/(line), *s = bak;

    for(int in_string = 0, seen_alphanum = 0; *line; ++line ) {
        if( !in_string ) {
            // early return till eos if literal /// comment is found.
            if( line[0] == '/' && line[1] == '/' && line[2] == '/' ) {
                strcpy(s, line);
                return bak;
            }
            // detect string open
            if( *line == '\"' || *line == '\'' ) if( line[-1] != '\\' ) {
                in_string = *line;
                *s++ = *line;
                continue;
            }
            // remove spaces
            if( *line > 0 && *line <= 32 ) {
                // keep spacing only if previous char was alphanum && next char is not operator
                const char *next_nbsp = line+1 + strspn(line+1, " \t\r\n");
                #define PUNCT_CHARSET "#" "!&|~" "+-/*%()" "<=>" "[]{}" "?:;" ",'\"\\"
                if(seen_alphanum && !strchr(PUNCT_CHARSET, *next_nbsp)) *s++ = ' ';

                while( *line > 0 && *line <= 32 ) ++line;

                --line;
                continue;
            }
            // save for later
            seen_alphanum = isalnum(*line);
            *s++ = *line;
        } else {
            while(*line == '\\' && line[1]) { memcpy(s, line, 2); s += 2; line += 2; }
            if(*line == in_string ) in_string = 0;

            seen_alphanum = isalnum(*line);
            *s++ = *line;
        }
    }

    *s = 0;
    return bak;
}

bool parse_c_function(const char *raw, int *out_name_pos, int *out_name_len, int *out_args_pos, bool clean_parens) {
    // try to clean up name from worst pathological case with parentheses. ie:
    // API __declspec(dllexport) int(*)(int,...)(MACRO_CALLBACK)(int(*my_callback)(int,...));
    // split declaration into 3 parts: return value, function name, and arguments.
    // 1. string is parsed backwards, from tail to head.
    // 2. arguments are parsed till all matching ( ) chars are balanced.
    // 3. function name is parsed then till '(' or ' ' are found. we remove any parens if present.
    // 4. everything else is the return type.
    bool is_function = false;
    int name = 0, args = 0;
    const char *eos = raw + strlen(raw) - 1;
    for( int i = eos - raw, open = 0; i >= 0; --i ) {
        if( raw[i] == ')' ) ++open;
        if( raw[i] == '(' ) if(open > 0) --open;

        if( raw[i] == '(' && open == 0 ) {
            /**/ if( args == 0 ) args = i;
            else if( name == 0 ) name = i;
        }
        if( (raw[i] == ' ' || raw[i] == '*') && open == 0 && args != 0 && name == 0 ) {
            name = i;
        }

        if( i == 0 ) is_function = (open == 0); // if parens are not balanced, this is not a function
    }
    if( is_function ) {
        // printf("%d %d\n", name, args);
        // printf(">> %.*s\n", args-name, raw+name);
        // printf(">> %s\n", raw + args);
        *out_args_pos = args;
        *out_name_pos = name;
        *out_name_len = args-name;
        if( clean_parens && raw[name] == '(' && raw[args-1] == ')' ) {
            *out_name_pos += 1;
            *out_name_len -= 2;
        }
        return true;
    }
    return false;
}

// reintroduce spacing and tabs where due, so code is pretty much readable again.
// some difficult notes when indenting7cleaning up:
// 1. structs, unions, enums use '{' to increase indenation, and '}' to decrease it.
// 2. structs, unions do line carriage after every member declaration, and keep indentation.
// 3. enums use ',' to linefeed
// 4. we do conversion from static [inline] type func() { ... } -> to -> type func();
// 5. rename functions that are declared between parens to disambiguate macros: int (print)(...);
// 6. do ellipsis on macro contents: #define print(args) ...
char *prettify(char *raw, char **opt_name) {
    if( strbegi(raw, "/""//") ) { raw[0] = 0; return raw; }
    if( strstr(raw, "/""//") ) { *strstr(raw, "/""//") = '\0'; }

    enum { TABS = 4 };

    if(DO_DEBUG) puts(raw);

    static char *buf = 0; buf = REALLOC(buf, strlen(raw) * 16 + 256);
    char *s = buf;

    // hide implementation from static functions
    int is_static = strbegi(raw, "static ");
    int is_static_inline = strbegi(raw, "static inline ");
    if( is_static || is_static_inline ) {
        raw += strlen(is_static_inline ? "static inline " : "static ");
        char *eos = strchr(raw, '{');
        if(eos) eos[0] = ';', eos[1] = 0;
    }

    // hide macro details. expose only name and args
    int is_macro = strbegi(raw, "#define ");
    if( is_macro ) {
        if( raw[8] == '_' && raw[8+1] == '_' ) return raw[0] = 0, raw; // do not process compiler __symbols
        #define KEYWORD_CHARSET "_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
        #define MACRO_CHARSET KEYWORD_CHARSET "$"
        int wordlen = strspn(raw+8, MACRO_CHARSET);
        for( int i = 8 + wordlen, open = 0; raw[i]; ++i ) {
            if( raw[i] == '(' ) { ++open; continue; }
            if( raw[i] == ')' ) { if(open > 0) --open; continue; }
            if( open == 0 ) {
                // hide implementation, until either any of eos or ///comment are found
                while( raw[i] && strcmp(raw+i, "/""//") ) {
                    raw[i++] = ' ';
                }
                break;
            }
        }
        memcpy(raw+=2, "macro", 5); // #define -> macro
    }

    int is_function = raw[0] != '#' && !strchr(raw, '{') && !strchr(raw, '}') && strchr(raw, '(') && strchr(raw, ')') && !strbegi(raw, "typedef");
    if( is_function ) {
        int name = 0, args = 0, namelen = 0;
        is_function = parse_c_function( raw, &name, &namelen, &args, false );
        // clean parens `int (function)(...);` cases
        if( is_function ) { 
            if( raw[name+namelen-1] == ')' ) {
            sprintf(buf, "%.*s%s%.*s%s", name, raw, raw[name-1] == '*' ? "":" ", namelen-2, raw+name+1, raw+args);
            raw = strdup(buf);
            *buf = 0;
            }
        }
    }

    int is_typedef        = strbegi(raw, "typedef "); 
    int is_typedef_enum   = strbegi(raw, "typedef enum");
    int is_typedef_union  = strbegi(raw, "typedef union");
    int is_typedef_struct = strbegi(raw, "typedef struct");
    int is_typedef_block  = is_typedef_enum || is_typedef_union || is_typedef_struct;
    if( is_typedef_block ) {
        raw += strlen("typedef "); // shorten decls on large structs/unions/enums
        char *eos = strrchr(raw, '}');
        if(eos) eos[1] = ';', eos[2] = 0;
    }

    int is_enum = strbegi(raw, "enum");

    const char *remaps[] = { // sort by strlen, then by priority
        "**", "** ",  // char**k
        "(*", " (*",  // int(*fn)();
        "*,", "*, ",  // int printf(char*,...);
        "*)", "*)",   // (char*)k here to fix ')' case below
        "*", "* ",    // char*k
        ",}", "}",    // enum{A,B,}
        ");", ");",   // int func(); here to fix ')' case below
        "};", "}; ",  // struct k{union{int k;};}; here to fix '}' case below
        "}",  "} ",   // typedef struct k{int k;}type;
        "{",  " { ",  // struct k{}
//      ",",  ", ",   // print(int a,int b)
        ";",  "; ",   // struct{int a;int b;};
        "=",  " = ",  // enum{N=1,}
    };
    // 1st pass, spacing
    for( int i = 0; raw[i]; ++i ) {
        *s++ = raw[i];
        for( int j = 0; j < countof(remaps) / 2; ++j ) {
            if(!strncmp(&raw[i], remaps[j*2+0], strlen(remaps[j*2+0]))) {
                --s;
                s += sprintf(s, "%s", remaps[j*2+1]);
                i += strlen(remaps[j*2+0]) - 1;
                break;
            }
        }
        // fix array(char*)strplit(...) or array(int)list(...) case
        // but dont in int(*callback_t)(...) case
        if( i > 0 && s[-1] == ')' && strchr(KEYWORD_CHARSET, raw[i+1]) ) {
            *s++ = ' ';
        }
    }
    if(DO_DEBUG) puts(buf);

    // prepare for 2nd pass
    *s++ = 0;
    raw =  buf + ALLOCSIZE(buf)-1 - strlen(buf)-1;
    strcpy(buf + ALLOCSIZE(buf)-1 - strlen(buf)-1, buf);
    s = buf;

    // 2nd pass, multi-lines
    for(int i = 0, level = 0; raw[i]; ++i) {
        int indent_inc = (raw[i] == '{');
        int indent_dec = (raw[i] == '}');
        int linefeed = indent_inc || indent_dec || (raw[i] == ';') || (is_enum && raw[i] == ',');

        if( !linefeed ) {
            *s++ = raw[i];
        } else {

            if( indent_dec ) {
                if( is_enum ) s += sprintf(s, "\n%*s", (level-1)*TABS, "");
                else          if( level > 0 ) s -= TABS;
            }

            /**/ if( indent_inc ) s += sprintf(s, "%c%s\n%*s", raw[i], raw[i+1] == ';' ? ";" : "", ++level * TABS, "");
            else if( indent_dec ) s += sprintf(s, "%c%s\n%*s", raw[i], raw[i+1] == ';' ? ";" : "", --level * TABS, "");
            else                  s += sprintf(s, "%c%s\n%*s", raw[i], raw[i+1] == ';' ? ";" : "",   level * TABS, "");

            // consume semicolon
            while(raw[i+1] == ';') ++i;
            // consume whitespaces
            while(raw[i+1] == ' ') ++i;
        }
    }
    // if(DO_DEBUG) puts(buf); // no need to debug this one, gonna be printed anyways

    // prepare for last pass
    *s = 0;
    s = buf;
    // last pass: remove extra linefeeds at eos
    while( s[strlen(s)-1] == '\n' ) s[strlen(s)-1] = 0;

    // extract type+name if needed
    if( opt_name ) {
        const char *line = buf;
        bool is_function = strchr(line, '(') && strchr(line, '(') < strrchr(line, ')');

        char s[1024]; int n,nl,a;
        /**/ if( line[0] == '#' )          { sscanf(line, "%*s %s", s);   *opt_name = stringf("#%s",s); }
        else if( strbegi(line, "macro"))   { sscanf(line, "%*s %[^(]",s); *opt_name = stringf("m%s",s); }
        else if( strbegi(line, "union"))   { sscanf(line, "%*s %s",s);    *opt_name = stringf("u%s",s); }
        else if( strbegi(line, "struct"))  { sscanf(line, "%*s %s",s);    *opt_name = stringf("s%s",s); }
        else if( strbegi(line, "enum"))    { sscanf(line, "%*s %s",s);    *opt_name = stringf("e%s",s); }
        else if( strbegi(line, "typedef")) { 
            char *last_word = strrchr(line, ' ') + 1;
            sscanf(last_word, "%[^[;]", s);
            if( strchr(s, ')') ) {
                line += strlen("typedef ");
                parse_c_function(line,&n,&nl,&a,1);
                line += n;
                if(*line == '*') line++, --nl;
                sprintf(s, "%.*s", nl, line);
            }
            *opt_name = stringf("t%s",s);
        }
        else if( line[0] != '#' && is_function && parse_c_function(line,&n,&nl,&a,1)) { 
            while(line[n] == ' ' || line[n] == '*') ++n, --nl; 
            sprintf(s,"%.*s",nl,line+n); 
            *opt_name = stringf("f%s",s);
        }
        // else isvar {}

        // trim whitespaces
        if(*opt_name && strchr(*opt_name, ' ')) {
        *strchr(*opt_name, ' ') = 0;
        }

        // disambiguate macros()<->defines
        if(*opt_name && *opt_name[0] == 'm') {
            #if 1
            char upper[128] = {0}, *u = upper, lower[128] = {0}, *l = lower;
            for( char *p = *opt_name+1; *p; ++p) {
                *u++ = toupper(*p);
                *l++ = tolower(*l);
            }
            if( !strcmp(lower, *opt_name+1) ) *opt_name[0] = 'm';
            if( !strcmp(upper, *opt_name+1) ) *opt_name[0] = 'd';
            #else
            char *found = strstr(line, va("%s(", (*opt_name)+1));
            if(!found)
            *opt_name[0] = 'd';
            #endif
        }
    }

    return buf;
}

int sort_strcmp( const void *str1, const void *str2 ) {
    const char *const *pp1 = str1;
    const char *const *pp2 = str2;
    return strcmpi(*pp1, *pp2);
}

// db cache
array(char*) description;
array(char*) arguments;
array(char*) returns;
array(char*) example;
array(char*) links;
array(char*) notes;

array(char*) defines;
array(char*) enums;
array(char*) types; // unions,structs,typedefs
array(char*) functions;
array(char*) macros;

char *learn(char *line) {
    // @todo: learn markdeep from [line] based on array(context)
    // @todo: update caches & context
    // [...]

    // continue with pretty line
    char* typename = 0;
    char *pretty = prettify(strdup(line), &typename);

if( strbegi(line, "/""//") && line[3] != '/' ) {
    char *md = line + 3 + (line[3] == ' ');

    if( strbegi(md, "returns" ) ) {
        // inline replacement
        memcpy(md, "return:", 7);
    }

    // grab links & truncate line if needed
    if( strstr(md, "see:") ) {
        char *found = strstr(md, "see:");
        array(char*) tokens = strsplit(found+4, " ,.");
        for( int i = 0, end = array_count(tokens); i < end; ++i) {
            array_push(links, tokens[i]);
        }
        *found = '\0';
    }

    if( strbegi(md, "example:") ) {
        array_push(example, va("%s", md+8));
    }
    else if( strbegi(md, "> ") ) {
        array_push(example, va("%s", md+2));
    }
    else if( strbegi(md, "!!!! ") ) {
        array_push(notes, va("%d%s", 4, md+5));
    }
    else if( strbegi(md, "!!! ") ) {
        array_push(notes, va("%d%s", 3, md+4));
    }
    else if( strbegi(md, "!! ") ) {
        array_push(notes, va("%d%s", 2, md+3));
    }
    else if( strbegi(md, "! ") ) {
        array_push(notes, va("%d%s", 1, md+2));
    }
    else if( md[0] ) {
        char heading[512]; sscanf(md, "%s", heading);
        char *text = heading[0] && strendi(heading, ":") ? md+strlen(heading) : 0;
        if( text ) {
            if( strbegi(heading, "return") ) {
                array_push(returns, text);
            } else {
                text[-1] = '\0'; // remove ':'
                array_push(arguments, md); // heading
                array_push(arguments, text);
            }
        } else {
            array_push(description, md);
        }
    }
}
else if( typename ) {
    if( typename[0] == 'd' ) array_push(defines, typename+1);
    if( typename[0] == 't' ) array_push(types/*typedefs*/, typename+1);
    if( typename[0] == 'e' ) array_push(enums, typename+1);
    if( typename[0] == 's' ) array_push(types/*structs*/, typename+1);
    if( typename[0] == 'u' ) array_push(types/*unions*/, typename+1);
    if( typename[0] == 'f' ) array_push(functions, typename+1);
    if( typename[0] == 'm' ) array_push(macros, typename+1);
}

    return line;
}

bool group_together() {
    // find relevant syntax blocks that belong together:
    // - they are a group if lines are sticked together, with no newlines.
    // - they are part of a group if both syntax blocks are sharing keywords on them.
    return true;
}

void print_markdeep(const char *line) { // "/// #hello markdeep"
    const char *text = line + 3 + (line[3] == ' ');
    const char *word = text + strspn(text, " \t");
    if( !word[0] ) return;

    int needs_blank =
        strbegi(word, "---") || strbegi(word, "~~~") || strbegi(word, "```");

    int needs_heading =
        strbegi(word, "# ") || // section
        strbegi(word, "**") || strbegi(word, "* ") || // heading and diagrams
        strbegi(word, "- ") || strbegi(word, "1. ") || // un/ordered lists
        strbegi(word, "---") || strbegi(word, "~~~") || strbegi(word, "```") // linebreaks and snippets
            ? 0 : 1;

    printf("%s%s%s\n", needs_blank ? "\n":"", needs_heading ? "- ":"", text);
}

void print_decl(char *line) { // non-const, since prettify can modify input
    if(strbegi(line, "/""//")) return;

    char *typename;
    char *pretty = prettify(line, &typename);

    char type = typename[0], *name = typename+1;

    char *header = "";
    /**/ if( type == 'd' ) header = "๐Ÿ„ณ"; //
    else if( type == 't' ) header = "๐Ÿ…ƒ"; // ๐Ÿ…ฃ
    else if( type == 'e' ) header = "๐Ÿ„ด"; // ๐Ÿ…”
    else if( type == 'u' ) header = "๐Ÿ…‚"; // ๐Ÿ…ข
    else if( type == 's' ) header = "๐Ÿ…‚"; // ๐Ÿ…ข
    else if( type == 'f' ) header = "๐Ÿ„ต"; // ๐Ÿ…•โ’ป
    else if( type == 'm' ) header = "๐Ÿ„ผ"; // ๐Ÿ…œ

    // make pretty->oneliner
    bool truncated = 0;
    char *oneliner = CALLOC(1, strlen(pretty) + 64); strcpy(oneliner, pretty);
    if( strchr(oneliner, '\n') ) { // union,struct,enum
        char *patch = strstr(oneliner, name);
        if( patch ) patch += strlen(name), *patch++ = ';', *patch++ = 0, truncated = 1;
    }
    else // typedef
    if( strbegi(oneliner, "typedef") && strchr(oneliner, '(') ) {
        sprintf(oneliner, "typedef %s;", name); // truncate pointer-to-functions to max
        truncated = 1;
    }
    else // function
    if( strchr(oneliner, '(') ) {
#if 0
        // ret name(args); >> name(args)->ret
        char *name_args = va("%s%s", name,strstr(pretty,name)+strlen(name));
        char *ret = va("%.*s", (int)(strstr(pretty,name)-pretty),pretty);
        sprintf(oneliner, "%.*s; // -> %s", (int)strlen(name_args)-1, name_args, ret);
        truncated = 1;
#elif 0
        // ret name(args); >> name(args)->ret
        char *open = va("%s(", name);
        char *ret = va("%.*s", (int)(strstr(pretty,open)-pretty),pretty);
        char *args = va("%s", strstr(pretty,name)+strlen(name));
//        sprintf(oneliner, "%16s %-16s%s", ret, name, args);  //      API void print44     (mat44 m)
//        sprintf(oneliner, "%-16s %-16s%s", ret, name, args); // API void      print44     (mat44 m)
          sprintf(oneliner, "%-16s %s%s", ret, name, args);
        strrepl(&oneliner, " ", "ยท"); // windows 0xA0 character nbsp, 0xB7 char middle dot
        truncated = 1;
#endif
    }
    else // macro, define
    {}

    // anchor
    printf("<a name=\"%s\"></a>\n", name);

    // functions
    printf("<details><summary><code lang=C>%s %s</code></summary>\n", header, oneliner);

    if( truncated ) {
        printf("~~~~~~C\n"); // linenumbers\n");
        printf("%s\n", pretty); // header, pretty);
        printf("~~~~~~\n");
    }

    if(1) {
        if( description ) {
            puts("");
            for( int i = 0, end = array_count(description); i < end; ++i) {
                description[i][0] = toupper(description[i][0]);
                printf("%s%s\n", description[i], strendi(description[i], ".") || strendi(description[i], ". ") ? "":".");
            }
        }
        if( arguments || returns ) {
            char *glue = returns ? strjoin(returns, ". ") : "";

            glue[0] = toupper(glue[0]);

            if( !arguments ) {
                printf("\nReturns: %s\n", glue);
            } else {
                const char *head =
                    type == 'e' ? "Enum" :
                    type == 'u' || type == 's' ? "Member" :
                    /*type == 'f' ? */ (glue ? "Return" : "Argument");

                printf("\n\n|%s|%s|\n", head, glue);
                printf("|-----|------|\n");
                for( int i = 0, end = array_count(arguments)/2; i < end; ++i ) {
                    char *arg = arguments[i*2+0]; // arg[0] = toupper(arg[0]);
                    char *nfo = arguments[i*2+1]; nfo[0] = toupper(nfo[0]);
                    printf("|`%s`|%s|\n", arg, nfo);
                }
            }
        }
        if( notes ) {
            for( int i = 0, end = array_count(notes); i < end; ++i) {
                int level = notes[i][0] - '0';
                const char *severity[] = { "Note", "Note", "Tip", "WARNING", "ERROR" }; 
                notes[i][1] = toupper(notes[i][1]);
                if( level < 4 )
                printf("!!! %s\n%4s%s\n", severity[ level ], "", notes[i]+1);
                else
                printf("!!! %s: ALERT\n%4s%s\n", severity[ level ], "", notes[i]+1);
            }
        }
        if( example ) {
            puts("\n~~~~~~C linenumbers");
            printf("/* %s example */\n", name);
            for( int i = 0, end = array_count(example); i < end; ++i) {
                puts(example[i]);
            }
            puts("~~~~~~\n");
        }
        if( links ) {
            printf("\nSee also: ");
            const char *sep = "";
            for( int i = 0, end = array_count(links); i < end; ++i) {
                int slen = strlen(links[i]);
                char is_external = links[i][slen-1] == '^';
                if( is_external ) {
                    printf("%s<a href=\"https://google.com/search?q=%.*s\" target=\"_blank\">%.*s</a>", sep, slen-1, links[i], slen-1, links[i]);
                } else {
                    printf("%s[%s](#%s)", sep, links[i], links[i]);
                }
                sep = ", ";
            }
            puts("");
        }

        if(!(description || example || notes || links || returns || arguments)) {
            puts("\nUnder construction. Yet to be documented.");
            puts("\nOther documentation examples: [dll](#dll), [strsplit](#strsplit), [strjoin](#strjoin), [IMAGE_FLAGS](#IMAGE_FLAGS) or [image_t](#image_t).");
        }

        array_free(description);
        array_free(example);
        array_free(notes);
        array_free(links);
        array_free(returns);
        array_free(arguments);
    }

    printf("\n</details>\n\n");
}

int main(int argc, char **argv) {
    // write header+intro
    DO_CSS = flag("-nocss") ? 0 : optioni("--css", DO_CSS);
    DO_DEBUG = optioni("--debug", DO_DEBUG) | flag("-dbg");
    const char *excludes = option("--excluded-sections,--excluded,-x", "");

    if( DO_CSS ) {
        const char *paths[] = {
            "editor/docs/",
            "art/docs/",
            "tools/docs/",
        };
        char *css = 0, *md = 0;
        for( int i = 0; i < countof(paths); ++i ) {
            char *path_md = va("%sdocs.md", paths[i]);
            char *path_css = va("%sdocs.css", paths[i]);
            if(file_exist(path_md))  md  = file_read(path_md);
            if(file_exist(path_css)) css = file_read(path_css);
        }
        if(!css || !css[0]) exit(-puts("error! cannot find docs.css"));
        if(!md || !md[0]) exit(-puts("error! cannot find docs.md"));
        
        strrepl(&css, "\r\n", "\n");
        strrepl(&md, "\r\n", "\n");
        puts( css );
        puts( preprocess_md(md) );
    }
    if( DO_README ) {
        char *md = file_read("README.md");
        strrepl(&md, "\r\n", "\n");
        // seek to first header
        md = strstr(md, "## ");
        // process headers
        while( md && *md ) {
            char *header = strstr(md, "## ") + 3;
            char *footer = strchr(header, '\n'); if(!footer) footer = header + strlen(header) - 1;
            printf("</details>\n\n<details><summary>%.*s</summary>\n\n", (int)(footer - header), header);
            md = strstr(footer, "## ");
            if( md ) printf("%.*s", (int)(md - footer), footer);
            else printf("%s</details>\n\n", footer);
        }
    }

    // headers are enabled by default. sources do not.
    const char *fname = argc > 1 ? argv[1] : __FILE__;
    int is_header_file = 1; // strstr(".h.H", file_ext(fname)) ? 1 : 0;
    char *section = STRDUP(file_name(fname));

    // read & preprocess C file
    char *raw = file_read(fname);
    preprocess_c(raw);
    // if(DO_DEBUG) fprintf(stderr, "%s\n", raw);

    // split into lines
    array(char*) split = strsplit(raw, "\r\n");
    if(DO_DEBUG) fprintf(stderr, "split %d lines\n", (int)array_count(split));

    // minify every line, then learn & cache
    array(char*) file = 0;
    for(int i = 0; i < array_count(split); ++i) {
        char *line = minify(split[i]);
        if( !line[0] ) continue;

        is_header_file |= (strbegi(line, "#pragma once") || strbegi(line, "#line")); // likely a header file
        if(!is_header_file) continue;

        // handle new heading sections
        if( strbegi(line, "#line") ) {
            char newfile[128];
            if(sscanf(line, "%*s %*d %[^\r\n]", newfile) == 1 ) {
                // remove quotes if present
                char *p = &newfile[ newfile[0] == '\"' ];
                if(strrchr(p, '\"')) *strrchr(p, '\"') = 0;
                if(strrchr(p, '/')) p = strrchr(p, '/')+5;
                // store
                section = STRDUP(p);
            }
        }
        // do not process if section is excluded from processing...
        if( section && excludes[0] ) {
            bool is_in_list = !!strstr(excludes, section);
            bool is_whole_list = !strcmp(excludes, section);
            if( is_whole_list || is_in_list ) {
                continue;
            }
        }
        // ...else, process if needed
        if( section ) {
            // remove extension if present
            char *found = strrchr(section, '.'); if(found) *found = 0;
            // print section in Uppercase form
            char *name = section + (strbegi(section, "v4k_") ? 4 : 0);
            //name[0] = toupper(name[0]);
            printf("## %s\n\n", name);
            // reset
            section = NULL;
        }

        if(strstr/*strendi*/(line, "/""//-")) continue; // discard explicitly excluded lines

        learn(line);

        bool is_macro = line[0] == '#';
        bool is_enum = strbegi(line, "enum ") || strbegi(line, "enum{");
        bool is_struct = strbegi(line, "struct ") || strbegi(line, "struct{");
        bool is_union = strbegi(line, "union ") || strbegi(line, "union{");
        bool is_block = is_enum || is_struct || is_union;
        bool is_variable = !!strstr(line, "extern ");
        bool is_function = !is_macro && strchr(line, '(') && strchr(line, '(') < strrchr(line, ')');
        bool is_typedef = !is_block && strbegi(line, "typedef ");

        // @todo: sort out display priorities

        // if(DO_DEBUG) printf("L%d [%s]\n", i, line);

        char *request = strstr(line, "/""//+");
        if( request ) {
            // explicit request
            request[3] = ' '; // hide plus sign
            print_markdeep(request);
            request[0] = 0;
            // ...then print decl
            print_decl(line);
            continue;
        }

        if( is_variable || is_function || is_typedef ) {
            print_decl(line);
            continue;
        }
        if( is_block ) {
            print_decl(line);
            continue;
        }
    }

    puts("\n## ๐Ÿ„ณ defines");
    array_sort(defines, sort_strcmp);
    array_unique(defines, sort_strcmp);
    { char *sep = ""; for(int i = 0, end = array_count(defines); i < end; ++i)    printf("%s[%s](#%s)", sep, defines[i], defines[i]), sep = ", "; }

    puts("\n## ๐Ÿ„ด enums");
    array_sort(enums, sort_strcmp);
    array_unique(enums, sort_strcmp);
    { char *sep = ""; for(int i = 0, end = array_count(enums); i < end; ++i)      printf("%s[%s](#%s)", sep, enums[i], enums[i]), sep = ", "; }

    puts("\n## ๐Ÿ…ƒ types");
    array_sort(types, sort_strcmp);
    array_unique(types, sort_strcmp);
    { char *sep = ""; for(int i = 0, end = array_count(types); i < end; ++i)   printf("%s[%s](#%s)", sep, types[i], types[i]), sep = ", "; }

    puts("\n## ๐Ÿ„ต functions");
    array_sort(functions, sort_strcmp);
    array_unique(functions, sort_strcmp);
    { char *sep = ""; for(int i = 0, end = array_count(functions); i < end; ++i)  printf("%s[%s](#%s)", sep, functions[i], functions[i]), sep = ", "; }

    puts("\n## ๐Ÿ„ผ macros");
    array_sort(macros, sort_strcmp);
    array_unique(macros, sort_strcmp);
    { char *sep = ""; for(int i = 0, end = array_count(macros); i < end; ++i)     printf("%s[%s](#%s)", sep, macros[i], macros[i]), sep = ", "; }

    if ( DO_CHANGELOG ) {
        printf("\n## c h a n g e l o g\n\n");
        char *chg = file_read("changelog.txt");
        strrepl(&chg, "\r\n", "\n");
        for each_substring(chg, "\n", it) {
            if (strstr(it, "sync depot")) continue;
            if (strstr(it, "sync website")) continue;
            if (strstr(it, "sync fwk")) continue;
            if (strstr(it, "sync FWK")) continue;
            // printf("<details><summary>%s</summary></details>\n", it);
            printf("* %s\n", it);
        }
    }

    puts("\n<script>");
    puts("\nmarkdeepOptions = {");
    puts("\n    tocStyle:'medium', /* auto,none,short,medium,long */");
    puts("\n    definitionStyle:'auto',  /* auto,short,long */");
    puts("\n    linkAPIDefinitions: true, /* true */");
    puts("\n};");
    puts("\n</script>");
    puts("\n<!-- Markdeep: --><script src=\"https://morgan3d.github.io/markdeep/1.13/markdeep.min.js?\" charset=\"utf-8\"></script>");

    fprintf(stderr, "%s\n", "Ok");
}

// full example below:

#line 1000 "docs.h"

/// # This is a demo
/// Below you can find a diagram that illustrates the whole process.
/// *************************************
/// *                                   *
/// * +----------> [] ----+             *
/// * |                   |             *
/// * +-------------------+             *
/// *                                   *
/// *************************************

/* this is a C comment
*/

#   include\
<stdio.h>
  #  define my_test(...) \
    do_nothing

/// this is my image class
struct my_image_t {
    unsigned int width, height, bpp : 10;
    void *pixels;
    union {
        int dummy;
    };
};

typedef struct my_image_t my_image;

/// this is an enumeration
enum my_enumeration {
    ONE = 1,
    TWO = 2,
    THREE,
    FOUR
};

#define my_macro_exposed1() ///
#define my_macro_exposed2() ///exposed with a comment
#define my_macro_not_exposed() ///-

/// this function acts like printf. `fmt` is a C formatted string.
void function_exposed1(char *fmt, ...);
map(char*,array(int)) (function_exposed2)(
    int line1,
    int line2,
    ...
);

array(char*) function_exposed_3(int line);

extern int version;

///------------------