// *** Here be dragons! ***
// This code is mostly a proof of concept that ended up working pretty well.
// I'm sure there's a ton of edgecases we'll run into as the base library grows,
// but it works for now.

UseLocalLinks :: false; // set to false when deploying the docs

#scope_file #run {
   set_build_options_dc(.{ do_output = false });

   ws := compiler_create_workspace();
   opts := get_build_options(ws);
   opts.output_type = .NO_OUTPUT;
   set_build_options(opts, ws);

   compiler_begin_intercept(ws);
   add_build_string(#string END
      // Add freestanding modules here.
      // Other modules will be automatically generated if they're imported by someone else.

      _ :: #import "jc";
      _ :: #import "jc/math";
      _ :: #import "jc/fmt/base64";

      _ :: #import "jc/x/json";

      _ :: #import "jc/ext/hmm";
      _ :: #import "jc/ext/luajit";
      _ :: #import "jc/ext/raylib";
      _ :: #import "jc/ext/remotery";
      // _ :: #import "jc/ext/darwin";
      // _ :: #import "jc/ext/objc";
      // _ :: #import "jc/ext/win32";
   END, ws);

   CheckImport :: (import: *Code_Directive_Import) -> bool {
      return (BelongsToJc(import) || contains(import.name, "jc")) && !(import.flags & .UNSHARED);
   }

   BuildImport :: (import: *Code_Directive_Import) -> Import {
      return .{
         node     = import,
         name     = import.name,
         external = !contains(import.name, "jc"),
      };
   }

   while true {
      msg := compiler_wait_for_message();
      if msg.workspace != ws continue;
      if msg.kind == .COMPLETE || msg.kind == .ERROR break;

      if msg.kind == {
         case .TYPECHECKED;
            tc := msg.(*Message_Typechecked);
            for tc.others if BelongsToJc(it.expression) {
               if it.expression.kind == {
                  case .DIRECTIVE_IMPORT;
                     import := it.expression.(*Code_Directive_Import);
                     if CheckImport(import) {
                        name := ModuleName(import);
                        if name.count == 0 continue;

                        mod := GetModule(name);
                        array_add_if_unique(*mod.imports, BuildImport(import));
                     }

                  case .DIRECTIVE_MODULE_PARAMETERS;
               }
            }

            for tc.declarations if BelongsToJc(it.expression) {
               decl := it.expression;
               if !(decl.flags & .IS_GLOBAL) continue;
               if decl.flags & .SCOPE_FILE   continue;

               ignored := false;
               for decl.notes if it.text == "jc.nodocs" {
                  ignored = true;
                  break;
               }

               // Ignore this decl if explicit instructed or
               // if the expression isn't something we should document.
               if ignored || !(ValidNode(decl.expression) && BelongsToJc(decl.expression))
                { continue; }

               name := ModuleName(decl);
               if name.count == 0 continue;

               mod := GetModule(name);

               expr := decl.expression;
               if expr.kind == {
                  case .STRUCT; #through;
                  case .ENUM;
                     array_add(*mod.types, .{
                        decl   = decl,
                        node   = expr,
                        source = CleanJaiSource(decl),
                        docs   = DocComments(decl),
                     });

                  case .PROCEDURE_HEADER;
                     array := *mod.procs;
                     header := expr.(*Code_Procedure_Header);
                     if header.procedure_flags & .MACRO {
                        array = *mod.macros;
                     }

                     // there should really be a flag on the proc header...
                     first := to_lower(decl.name[0]);
                     if !(first >= #char "a" && first <= #char "z") {
                        decl.name = tprint("operator%", decl.name);
                     }

                     array_add(array, .{
                        decl   = decl,
                        node   = header,
                        source = CleanJaiSource(decl),
                        docs   = DocComments(decl),
                     });

                  case;
                     if !(decl.flags & .IS_CONSTANT)
                      { continue; }

                     if decl.name == "RunTests" || decl.name == "RUN_TESTS"
                      { continue; }

                     array := *mod.consts;

                     // Things we don't want to be constant decls
                     if expr.kind == {
                        case .TYPE_INSTANTIATION;
                           array = *mod.types;

                        case .IDENT;
                           ident := decl.expression.(*Code_Ident);
                           expr  := ident.resolved_declaration.expression;
                           if expr.kind == .TYPE_DEFINITION {
                              array = *mod.types;
                           }

                        case .DIRECTIVE_IMPORT;
                           import := expr.(*Code_Directive_Import);
                           if CheckImport(import) {
                              array_add_if_unique(*mod.imports, BuildImport(import));
                           }

                           continue;
                     }

                     array_add(array, .{
                        decl   = decl,
                        node   = expr,
                        source = CleanJaiSource(decl),
                        docs   = DocComments(decl),
                     });
               }
            }
      }
   }

   compiler_end_intercept(ws);

   print("generating documentation...\n");

   // Render each module page
   for * mod, mod_name: modules {
      print("processing module '%'...\n", mod_name);
      ok := write_entire_file(tprint("docs/%", LinkableName(mod_name)), ModuleToHtml(mod));
      assert(ok, "failed to write documentation for module: %", mod_name);
   }

   // Render index.html
   b: String_Builder;
   StartPage(*b, tprint("Jc v%.% Documentation", JcMajor, JcMinor));
   print_to_builder(*b, "<h1>Jc v%.% Documentation</h1>", JcMajor, JcMinor);

   names: [..]string;
   for _, mod_name: modules {
      array_add(*names, mod_name);
   }

   contributors :: string.[
      "Judah Caruso",
      "Jesse Coyle",
   ];

   print_to_builder(*b, #string END
      <p>
      License: <a class='link' href='%1' target='_blank'>Public Domain</a>
      <br/>
      Repository: <a class='link' href='%2' target='_blank'>%2</a>
      <br/>
      Contributors: %3
      </p>
   END,
      "https://git.brut.systems/judah/jc/src/branch/master/LICENSE",
      "https://git.brut.systems/judah/jc",
      join(..contributors, ", "),
   );

   sorted_names := quick_sort(names, SortAlphabeticallyDescendingLength);

   append(*b, #string END
      <div class='index'>
   END);

   {
      append(*b, #string END
         <details open='true'>
            <summary><h3>Modules</h3></summary>
            <ul>
      END);


      for sorted_names if !contains(it, "jc/ext") {
         print_to_builder(*b, "<li><a href='%1'>%2</a></li>", LinkableName(it), it);
      }

      append(*b, #string END
         </ul>
         </details>
      END);
   }
   {
      append(*b, #string END
         <details>
            <summary><h3>Bindings</h3></summary>
            <ul>
      END);

      for sorted_names if contains(it, "jc/ext") {
         print_to_builder(*b, "<li><a href='%1'>%2</a></li>", LinkableName(it), it);
      }

      append(*b, #string END
         </ul>
         </details>
      END);
   }

   append(*b, "</div>");


   append(*b, #string END
<p>
   <strong>What:</strong><br/>
   A set of modules for things I usually want in Jai, namely, utilities, bindings, and experiments.
</p>

<p>
   <strong>Why:</strong><br/>
   Because Jai is still in closed beta (as of September, 2025),
   updates to the compiler and included modules break
   projects of mine; sometimes in very annoying ways.
   <br/>
   <br/>
   Jc was made to 1) give myself an escape hatch/skin-suit to
   cause fewer breaking changes when updating the compiler,
   and 2) put all of my non-project code in a single place
   that's easier to manage.
   <br/>
   <br/>
   While I do use many of the modules shipped with the
   compiler, my goal is to eventually replace them.
</p>

<p>
   <strong>How:</strong><br/>
   <pre>
# Direct installation
cd [jai install dir]/modules
git clone https://git.brut.systems/judah/jc.git

# Indirect installation
git clone https://git.brut.systems/judah/jc.git

ln -s "$(pwd)jc" [jai install dir]/modules/jc  # POSIX install
mklink /D "C:\path\to\jc" [jai install dir]\jc # Windows install

# Usage:
#import "jc";
#import "jc/[module]";
   </pre>
</p>
   END);

   EndPage(*b);

   ok := write_entire_file("docs/index.html", builder_to_string(*b));
   assert(ok, "failed to write docs/index.html");

   print("done!\n");
}

source_files: Table(string, []string);

GetSourceLines :: (filename: string) -> []string, bool {
   old_logger := context.logger;
   context.logger = (message: string, data: *void, info: Log_Info) {}; // do nothing logger
   defer context.logger = old_logger;

   fok, src := table_find_new(*source_files, filename);
   if fok {
      return src, true;
   }


   lines: []string;
   data, rok := read_entire_file(filename);
   if rok {
      lines = split(data, "\n");
      table_add(*source_files, filename, lines);
   }
   else {
      print("warn: file didn't exist '%'\n", filename);
   }

   return lines, rok;
}

modules: Table(string, Module);

GetModule :: (name: string) -> *Module {
   mod := find_or_add(*modules, name);
   mod.name = name;

   if mod.main_docs.count != 0
    { return mod; }

   lines, _ := GetSourceLines(tprint(".%/module.jai", string.{ data = name.data + 2, count = name.count - 2 }));
   if lines.count == 0 {
      mod.main_docs = " "; // non-empty so we don't try again
      return mod;
   }

// @todo(judah): this should handle preformatted text
   cleaned_lines: [..]string;
   blockbased := starts_with(lines[0], "/*");
   linebased  := starts_with(lines[0], "//") ;
   for lines {
      if blockbased {
         if trim(it) == "*/"
          { break; }
      }
      else if linebased {
         if !starts_with(it, "/")
          { break; }
      }
      else {
         break;
      }

      line := trim_right(trim_left(it, "/*"));
      if line.count > 0 {
         array_add(*cleaned_lines, line);
      }
   }

   mod.main_docs = join(..cleaned_lines, "\n");
   return mod;
}

Decl :: struct(T: Type) {
   using decl: *Code_Declaration;
   node:  *T;
   source: string;
   docs:   string;
}

Import :: struct {
   node:     *Code_Directive_Import;
   name:     string;
   external: bool;
}

operator== :: (a: Import, b: Import) -> bool {
   return a.name == b.name;
}

Module :: struct {
   name:      string;
   main_docs: string;
   imports:   [..]Import; // module import names
   types:     [..]Decl(Code_Node);
   consts:    [..]Decl(Code_Node);
   procs:     [..]Decl(Code_Procedure_Header);
   macros:    [..]Decl(Code_Procedure_Header);
}

LinkableName :: (mod_name: string) -> string {
   return tprint("%.html", to_lower_copy(replace(mod_name, "/", "_")));
}

ModuleToHtml :: (mod: *Module) -> string {
   Section :: (b: *String_Builder, name: string, decls: []Decl) -> bool {
      print_to_builder(b, "<h2 id='%1'><a href='#%1'>%2</a></h2>", to_lower_copy(name), name);

      if decls.count != 0 {
         return true;
      }

      append(b, "<p>This section is empty.</p>");
      return false;
   }

   sorted_consts  := quick_sort(mod.consts,  SortDeclsByName);
   sorted_procs   := quick_sort(mod.procs,   SortDeclsByName);
   sorted_macros  := quick_sort(mod.macros,  SortDeclsByName);
   sorted_types   := quick_sort(mod.types,   SortDeclsByName);
   sorted_imports := quick_sort(mod.imports, SortImportsByName);

   b: String_Builder;

   import_name := mod.name;
   short_name := path_basename(import_name);
   StartPage(*b, tprint("Module % &ndash; Jc v%.% Documentation", import_name, JcMajor, JcMinor));
   print_to_builder(*b, "<h1><a href='index.html'>[..]</a> Module &ndash; %</h1>", short_name);
   print_to_builder(*b, "<p>%</p>", mod.main_docs);

   if contains(import_name, "jc/x") {
      append(*b, "<span class='module-warning'>");
      print_to_builder(*b, "Warning: % is experimental and has no stability guarantees or versioning; use with caution.", short_name);
      append(*b, "</span>");
   }

   print_to_builder(*b, "<pre class='code'>#import \"%\";</pre>", import_name);

   PrintIndexFor :: (b: *String_Builder, name: string, decls: []Decl) {
      append(b, "<details>");
      print_to_builder(b, "<summary><h3>% (%)</h3></summary>", name, decls.count);
      append(b, "<ul>");
      for decls {
         print_to_builder(b, "<li><a href='#%1'>%1</a></li>", it.name);
      }
      append(b, "</ul>");
      append(b, "</details>");
   }

   append(*b, "<div class='index'>");
   append(*b, "<h2 id='index'><a href='#index'>Index</a></h2>");

   if sorted_consts.count PrintIndexFor(*b, "Constants", sorted_consts);
   if sorted_procs.count  PrintIndexFor(*b, "Procedures", sorted_procs);
   if sorted_macros.count PrintIndexFor(*b, "Macros", sorted_macros);
   if sorted_types.count  PrintIndexFor(*b, "Types", sorted_types);

   if sorted_imports.count {
      append(*b, "<details>");
      print_to_builder(*b, "<summary><h3>Imports (%)</h3></summary>", sorted_imports.count);
      append(*b, "<ul>");
      for sorted_imports {
         append(*b, "<li><a href='");

         if it.external {
            append(*b, NodeUrl(it.node));
         }
         else {
            append(*b, LinkableName(it.name));
         }

         print_to_builder(*b, "'>%</a></li>", it.name);
      }
      append(*b, "</ul>");
      append(*b, "</details>");
   }

   append(*b, "</div>");

   append(*b, "<div class='all-constants'>");
   if Section(*b, "Constants", sorted_consts) {
      for sorted_consts {
         append(*b, "<div class='constant'>");
         ConstantToHtml(*b, it);
         append(*b, "</div>");
      }
   }
   append(*b, "</div>");

   append(*b, "<div class='all-procedures'>");
   if Section(*b, "Procedures", sorted_procs) {
      for sorted_procs {
         append(*b, "<div class='procedure'>");
         ProcedureOrMacroToHtml(*b, it);
         append(*b, "</div>");
      }
   }
   append(*b, "</div>");

   append(*b, "<div class='all-macros'>");
   if Section(*b, "Macros", sorted_macros) {
      for sorted_macros {
         append(*b, "<div class='procedure'>");
         ProcedureOrMacroToHtml(*b, it);
         append(*b, "</div>");
      }
   }
   append(*b, "</div>");

   append(*b, "<div class='all-types'>");
   if Section(*b, "Types", sorted_consts) {
      for sorted_types {
         append(*b, "<div class='type'>");
         TypeToHtml(*b, it);
         append(*b, "</div>");
      }
   }
   append(*b, "</div>");

   EndPage(*b);
   return builder_to_string(*b);
}

#if UseLocalLinks {
   BaseUrl :: "docs";
   SrcPath :: "..";
}
else {
   BaseUrl :: "https://judahcaruso.com/jc";
   SrcPath :: "https://git.brut.systems/judah/jc/src";
}

NodeUrl :: (node: *Code_Node) -> string {
   path := node.enclosing_load.fully_pathed_filename;
   idx  := find_index_from_right(path, "jc/");
   assert(idx != -1);
   idx += 3;

   return tprint("%1/%2#L%3",
      SrcPath,
      string.{ data = path.data + idx, count = path.count - idx },
      node.l0,
   );
}

DeclTitle :: (b: *String_Builder, decl: Decl) {
   print_to_builder(b, #string END
      <h4 id='%1'>
         <a href='%2' target='_blank'>%1</a>
         <a href='#%1' class='short-link'>¶</a>
      </h4>
   END, decl.name, NodeUrl(decl));

   print_to_builder(b, "<pre class='code'>% :: %</pre>", decl.name, decl.source);
}

WriteDocsAndCode :: (b: *String_Builder, docs: string, example: string) {
   if docs.count != 0 {
      append(b, "<p>");
      append(b, docs);
      append(b, "</p>");
   }

   if example.count != 0 {
      append(b, "<details class='example'>");
      append(b, "<summary><p>Example</p></summary>");
      append(b, "<pre class='code'>");
      append(b, example);
      append(b, "</pre>");
      append(b, "</details>");
   }
}

ConstantToHtml :: (b: *String_Builder, decl: Decl) {
   DeclTitle(b, decl);

   doc_lines, example_lines := PrepareDocsAndCode(decl.docs);
   docs    := join(..doc_lines, "\n");
   example := join(..example_lines, "\n");
   WriteDocsAndCode(b, docs, example);
}

ProcedureOrMacroToHtml :: (b: *String_Builder, decl: Decl(Code_Procedure_Header)) {
   DeclTitle(b, decl);

   doc_lines, example_lines := PrepareDocsAndCode(decl.docs);
   for line, li: doc_lines {
      if starts_with(line, "Note:") {
         doc_lines[li] = tprint("<br/>%", line);
      }

      if starts_with(line, "See:") {
         doc_lines[li] = tprint("<br/>%", line);
      }
   }

   // horrible
   docs := join(..doc_lines, "\n");
   for decl.node.arguments {
      strs := [2]string.[
         .[ tprint(" % ", it.name), tprint(" <span class='ident'>%</span> ", it.name) ],
         .[ tprint("%.", it.name), tprint("<span class='ident'>%</span>.", it.name) ],
         .[ tprint(",%", it.name), tprint(",<span class='ident'>%</span>", it.name) ],
         .[ tprint("%,", it.name), tprint("<span class='ident'>%</span>,", it.name) ],
      ];

      for strs {
         docs = replace(docs, it[0], it[1]);
      }
   }

   example := join(..example_lines, "\n");
   WriteDocsAndCode(b, docs, example);
}

TypeToHtml :: (b: *String_Builder, decl: Decl) {
   DeclTitle(b, decl);
   doc_lines, example_lines := PrepareDocsAndCode(decl.docs);
   docs    := join(..doc_lines, "\n");
   example := join(..example_lines, "\n");
   WriteDocsAndCode(b, docs, example);
}

PrepareDocsAndCode :: (raw_docs: string) -> (doc_lines: []string, example_lines: []string) {
   if raw_docs.count == 0 {
      return .[], .[];
   }

   lines := split(raw_docs, "\n");

   doc_lines:     [..]string;
   example_lines: [..]string;

   example_start: int = -1;

   leading := 2;
   for lines {
      line := string.{ data = it.data + 3, count = it.count - 3 };
      if line.count <= 0 {
         array_add(*doc_lines, "");
         continue;
      }

      if line[0] == " " {
         line.data  += 1;
         line.count -= 1;
      }

      l := 0;
      while l < line.count if line[l] == " " {
         l += 1;
      } else {
         break;
      }

      if l > leading {
         leading = l;
         example_start = it_index;
         break;
      }

      array_add(*doc_lines, trim(line));
   }

   if example_start != -1 {
      for example_start..lines.count - 1 {
         line := trim_right(lines[it]);
         line.data  += 3 + leading;
         line.count -= 3 + leading;

         if line.count <= 0 {
            array_add(*example_lines, "");
            continue;
         }

         if line[0] == " " {
            line.data  += 1;
            line.count -= 1;
         }

         array_add(*example_lines, line);
      }
   }

   return doc_lines, example_lines;
}

ModuleName :: (node: *Code_Node) -> string {
   assert(node.enclosing_load != null);
   assert(node.enclosing_load.enclosing_import != null);

   import := node.enclosing_load.enclosing_import;
   return import.module_name;
}

SortDeclsByName :: (a: Decl($T), b: Decl(T)) -> int {
   return SortAlphabeticallyDescendingLength(a.name, b.name);
}

SortImportsByName :: (a: Import, b: Import) -> int {
   if a.external && !b.external
    { return 1; }

   if b.external && !a.external
    { return -1; }

   return SortAlphabeticallyDescendingLength(a.name, b.name);
}

SortAlphabeticallyDescendingLength :: (a: string, b: string) -> int {
   for 0..a.count-1 {
      if it >= b.count
       { return 1; }
      delta := a[it].(int) - b[it].(int);
      if delta
       { return delta; }
   }

   if b.count > a.count
    { return -1; }

   return 0;
}

CleanJaiSource :: (node: *Code_Node) -> string {
   assert(node.enclosing_load != null);
   assert(node.kind == .DECLARATION);
   decl := node.(*Code_Declaration);

   lines, ok := GetSourceLines(node.enclosing_load.fully_pathed_filename);
   assert(ok, "failed to get source: %", node.enclosing_load.fully_pathed_filename);

   // for procedures, strip out anything that doesn't help documentation.
   if decl.expression.kind == .PROCEDURE_HEADER {
      line := trim(lines[node.l0-1]);

      proc := decl.expression.(*Code_Procedure_Header);
      has_ret := proc.returns.count != 0;

      if has_ret {
         arrow_idx := find_index_from_right(line, "->");
         assert(arrow_idx != -1, "how?");

         i := arrow_idx;
         while i < line.count {
            if line[i] == "#" || line[i] == "{" {
               break;
            }

            i += 1;
         }

         line.count -= line.count - i;
      }
      else {
         while line.count > 0 {
            if line.data[line.count - 1] == ")" {
               break;
            }

            line.count -= 1;
         }
      }


      idx := find_index_from_left(line, "::");
      assert(idx != -1);

      head := string.{ data = line.data, count = idx };
      tail := string.{ data = line.data + head.count, count = line.count - head.count };

      while tail.count > 0 {
         if tail[0] == "(" {
            break;
         }

         tail.data  += 1;
         tail.count -= 1;
      }

      postfix: string;

      header := decl.expression.(*Code_Procedure_Header);
      if header.procedure_flags & .MACRO {
         postfix = " #expand";
      }

      if header.procedure_flags & .COMPILE_TIME_ONLY {
         postfix = tprint("% #compile_time", postfix);
      }

      if header.procedure_flags & .SYMMETRIC {
         postfix = tprint("% #symmetric", postfix);
      }

      return tprint("%1%2", trim(tail), postfix);
   }
   // for everything else, get the source range for the node and only strip out
   // comments that would've been caught by the doc comment finder.
   else {
      GetRangeEnd :: (node: *Code_Node, prev_l1: int) -> int, *Code_Node {
         if node.kind == {
            case .STRUCT;
               s := node.(*Code_Struct);
               return s.l1, s.block;

            case .ENUM;
               e := node.(*Code_Enum);
               return e.l1, e.block;

            case .BLOCK;
               return node.l1, null;

            case;
               // print("didn't handle: %\n", node.kind);
               return node.l1, null;
         }

         return -1, null;
      }

      start := node.l0-1;
      end   := -1;

      {
         node := decl.expression;
         while node != null {
            e, n := GetRangeEnd(node, node.l1);
            if n == null {
               end = e;
               break;
            }

            end  = e;
            node = n;
         }
      }

      assert(end != -1);

      cleaned_lines: [..]string;
      for start..end - 1 {
         line := lines[it];

         inline_comment_idx := find_index_from_right(line, "///");
         if inline_comment_idx != - 1 {
            if !starts_with(trim(line), "///") {
               line.count = inline_comment_idx;
            }
         }

         array_add(*cleaned_lines, line);
      }

      source := join(..cleaned_lines, "\n");
      index := find_index_from_left(source, "::");

      // @todo(judah): handle module parameters correctly
      if index == -1 {
         #import "Program_Print";
         b: String_Builder;

         is_module_param := true;
         if decl.expression != null {
            print_expression(*b, decl.expression);
         }
         else if decl.type_inst {
            print_expression(*b, decl.type_inst);
            is_module_param = false;
         }

         append(*b, ";");
         return builder_to_string(*b);
      }

      return trim(.{ data = source.data + (index + 2), count = source.count - (index + 2) });
   }
}

DocComments :: (node: *Code_Node) -> string {
   assert(node.enclosing_load != null);

   all_lines, ok := GetSourceLines(node.enclosing_load.fully_pathed_filename);
   assert(ok, "failed to get source: %", node.enclosing_load.fully_pathed_filename);

   main_line := all_lines[node.l0-1];

   inline_comment_idx := find_index_from_right(main_line, "///");
   if inline_comment_idx != -1 {
      return trim(.{
         data  = main_line.data  + inline_comment_idx,
         count = main_line.count - inline_comment_idx,
      });
   }

   start := -1;
   end   := (node.l0-2).(int);

   i := end;
   while i >= 0 {
      line := trim(all_lines[i]);
      defer i -= 1;

      if line.count == 0 || !starts_with(line, "///") {
         start = i + 1;
         break;
      }
   }

   if start == -1 {
      start = 0;
   }

   comment_lines: [..]string;
   for start..end {
      array_add(*comment_lines, trim(all_lines[it]));
   }

   return join(..comment_lines, "\n");
}

BelongsToJc :: (node: *Code_Node) -> bool {
   if node.enclosing_load == null || node.enclosing_load.enclosing_import == null {
      return false;
   }

   import := node.enclosing_load.enclosing_import;
   return contains(import.module_name, "jc") && ValidNode(node);
}

ValidNode :: (node: *Code_Node) -> bool {
   if node.kind == {
      case .DECLARATION;        #through;
      case .DIRECTIVE_IMPORT;   #through;
      case .DIRECTIVE_RUN;      #through;
      case .PROCEDURE_HEADER;   #through;
      case .STRUCT;             #through;
      case .TYPE_DEFINITION;    #through;
      case .TYPE_INSTANTIATION; #through;
      case .ENUM;               #through;
      case .LITERAL;            #through;
      case .IDENT;              #through;
      case .UNARY_OPERATOR;     #through;
      case .DIRECTIVE_MODULE_PARAMETERS; #through;
      case .BINARY_OPERATOR;
         return true;

      case;
         return false;
   }
}

StartPage :: (b: *String_Builder, title: string) {
   print_to_builder(b, #string END
      <html>
         <head>
         <meta charset="utf-8">
         <meta name="viewport" content="width=device-width, initial-scale=1">
         <meta name="color-scheme" content="light dark">
         <title>%1</title>
         <style>%2</style>
         <style>%3</style>
         </head>
         <body>
            <main>
   END, title, ResetCss, MainCss);
}

EndPage :: (b: *String_Builder) {
   append(b, #string END
         <main>
         <a href="#index" class="back-to-index" title="Back to top">↑</a>
      <body>
   <html>
   END);
}


MainCss :: #string END
:root {
  --bg-primary: #ffffff;
  --text-primary: #333333;
  --text-secondary: #666666;
  --border-color: #e1e4e8;
  --accent-color: #ec4899;
  --code-bg: #f6f8fa;
  --link-color: #ec4899;
  --link-hover: #db2777;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg-primary: #0d1117;
    --text-primary: #c9d1d9;
    --text-secondary: #8b949e;
    --border-color: #30363d;
    --accent-color: #f472b6;
    --code-bg: #161b22;
    --link-color: #f472b6;
    --link-hover: #f9a8d4;
  }
}

* {
  box-sizing: border-box;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', Arial, sans-serif;
  line-height: 1.6;
  color: var(--text-primary);
  background-color: var(--bg-primary);
  margin: 0;
  padding: 0;
  font-feature-settings: 'kern' 1, 'liga' 1, 'calt' 1;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1 {
  font-size: 2.5rem;
  font-weight: 600;
  margin: 0 0 2rem 0;
  border-bottom: 1px solid var(--border-color);
  padding-bottom: 0.5rem;
}

h2 {
  font-size: 1.8rem;
  font-weight: 600;
  margin: 2rem 0 1rem 0;
  padding-top: 1rem;
}

h4 {
  font-size: 1.1rem;
  font-weight: 600;
  margin: 1.5rem 0 1rem 0;
  font-family: 'Menlo', 'Monaco', 'SF Mono', 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
}

a {
  color: var(--link-color);
  text-decoration: none;
}

a:hover {
  color: var(--link-hover);
  text-decoration: underline;
}

h4 a {
  color: var(--text-primary);
}

h4 a:hover {
  color: var(--accent-color);
}

.procedure h4 a,
.type h4 a,
.constant h4 a {
  color: var(--accent-color);
}

.procedure h4 a:hover,
.type h4 a:hover,
.constant h4 a:hover {
  opacity: 0.8;
}

.procedure h4 a[href^="#"],
.type h4 a[href^="#"],
.constant h4 a[href^="#"] {
  color: var(--text-secondary);
  font-size: 0.9em;
}

.procedure h4 a[href^="#"]:hover,
.type h4 a[href^="#"]:hover,
.constant h4 a[href^="#"]:hover {
  color: var(--text-primary);
}

.index {
  margin: 2rem 0;
}

.index h2 {
  margin: 0 0 1rem 0;
  padding-top: 0;
}

.index h2 a {
  color: var(--text-primary);
}

.index h3 {
  font-size: 1.1rem;
  font-weight: 600;
  margin: 1rem 0 0.5rem 0;
  display: inline;
  cursor: pointer;
}

.index ul {
  margin: 0.25rem 0 0.5rem 1rem;
  padding: 0;
  list-style: none;
}

.index li {
  margin: 0;
}

.index a {
  color: var(--link-color);
  font-family: 'Menlo', 'Monaco', 'SF Mono', 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
}

.index a:hover {
  color: var(--link-hover);
}

.all-constants,
.all-procedures,
.all-macros,
.all-types {
  margin: 2rem 0;
}

.constant,
.procedure,
.macro,
.type {
  margin: 1.5rem 0;
}

pre.code {
  background-color: var(--code-bg);
  border: 1px solid var(--border-color);
  border-radius: 6px;
  padding: 1rem;
  margin: 1rem 0;
  overflow-x: auto;
  font-family: 'Menlo', 'Monaco', 'SF Mono', 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
  font-size: 0.9rem;
  line-height: 1.4;
  font-feature-settings: 'liga' 1, 'calt' 1;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

span.ident {
   color: var(--accent-color);
}

.example {
  margin: 1rem 0;
}

.example pre.code {
  margin: 0.5rem 0 0 0;
}

hr {
  border: none;
  height: 1px;
  background-color: var(--border-color);
  margin: 2rem 0;
}

details {
  margin: 0.5rem 0;
}

summary {
  cursor: pointer;
  font-size: 1.1rem;
  font-weight: 600;
  margin: 1rem 0 0.5rem 0;
}

summary::-webkit-details-marker,
summary::marker {
  display: none;
}

.example summary {
  font-size: 1rem;
  font-weight: 500;
  margin: 0.5rem 0;
}

.example summary p {
  margin: 0;
  display: inline;
}

.index details {
  margin: 0.25rem 0;
}

.index summary {
  margin: 0.5rem 0 0.25rem 0;
}

main {
  max-width: 900px;
  margin: 0 auto;
  padding: 2rem;
}

@media (max-width: 768px) {
  main {
    padding: 1rem;
  }

  h1 {
    font-size: 2rem;
  }

  h2 {
    font-size: 1.6rem;
    margin: 1.5rem 0 0.75rem 0;
  }

  h4 {
    font-size: 1rem;
    font-family: 'Menlo', 'Monaco', Consolas, 'Courier New', monospace;
  }

  .index a {
    font-family: 'Menlo', 'Monaco', Consolas, 'Courier New', monospace;
  }

  pre.code {
    font-family: 'Menlo', 'Monaco', Consolas, 'Courier New', monospace;
    font-size: 0.85rem;
    padding: 0.75rem;
    -webkit-text-size-adjust: 100%;
    text-rendering: optimizeLegibility;
  }
}

a:focus,
button:focus {
  outline: 2px solid var(--accent-color);
  outline-offset: 2px;
}

html {
  scroll-behavior: smooth;
}

.back-to-index {
  position: fixed;
  bottom: 2rem;
  right: 2rem;
  background-color: var(--accent-color);
  color: white;
  border: none;
  border-radius: 50%;
  width: 3rem;
  height: 3rem;
  font-size: 1rem;
  cursor: pointer;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
  opacity: 0.5;
  transition: all 0.3s ease;
}

.back-to-index:hover {
  opacity: 1;
  transform: translateY(-2px);
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
  color: var(--bg-primary);
  text-decoration: none;
}

.back-to-index:focus {
  outline: 2px solid var(--accent-color);
  outline-offset: 2px;
}

span.module-warning {
   background-color: var(--bg-primary);
   color: var(--accent-color);
   display: block;
   border-radius: 6px;
   font-weight: bold;
   border: 1px solid var(--accent-color);
   border-radius: 6px;
   padding: 0.5rem;
 }
END;

ResetCss :: #string END
/* Box sizing rules */
*,
*::before,
*::after {
  box-sizing: border-box;
}

/* Prevent font size inflation */
html {
  -moz-text-size-adjust: none;
  -webkit-text-size-adjust: none;
  text-size-adjust: none;
}

/* Remove default margin in favour of better control in authored CSS */
body, h1, h2, h3, h4, p,
figure, blockquote, dl, dd {
  margin-block-end: 0;
}

/* Remove list styles on ul, ol elements with a list role, which suggests default styling will be removed */
ul[role='list'],
ol[role='list'] {
  list-style: none;
}

/* Set core body defaults */
body {
  min-height: 100vh;
  line-height: 1.5;
}

/* Set shorter line heights on headings and interactive elements */
h1, h2, h3, h4,
button, input, label {
  line-height: 1.1;
}

/* Balance text wrapping on headings */
h1, h2,
h3, h4 {
  text-wrap: balance;
}

/* A elements that don't have a class get default styles */
a:not([class]) {
  text-decoration-skip-ink: auto;
  color: currentColor;
}

/* Make images easier to work with */
img,
picture {
  max-width: 100%;
  display: block;
}

/* Inherit fonts for inputs and buttons */
input, button,
textarea, select {
  font-family: inherit;
  font-size: inherit;
}

/* Make sure textareas without a rows attribute are not tiny */
textarea:not([rows]) {
  min-height: 10em;
}

/* Anything that has been anchored to should have extra scroll margin */
:target {
  scroll-margin-block: 5ex;
}

END;

#import "jc";

#import "String";
#import "Compiler";

using basic :: #import "Basic";
#poke_name basic operator==;

#import "File";
#import "Hash_Table";
#import "Sort";
