import { useEffect, useMemo, useState } from "react";
import { Link, useLocation } from "react-router-dom";
import { ChevronDown, ChevronRight, RefreshCw } from "lucide-react";
import {
  Button,
  DataGrid,
  DismissibleAlert,
  ExplorerTree,
  LoadingFrame,
  PageTitle,
  SegmentedControl,
  StatusBadge,
  adminErrorMessage,
  useGuardedNavigate,
  usePlatformLanguage,
  type ApiSettings,
  type DataGridColumn
} from "@govoplan/core-webui";
import {
  fetchDocsContext,
  type DocsContext,
  type DocsDocumentationTopic,
  type DocsModule,
  type DocsOptionalModuleEvidence,
  type DocsRoute,
  type DocsSource
} from "../../api/docs";

type DocumentationType = "admin" | "user";

type DocsPageNode =
  | {
      id: string;
      title: string;
      kind: "topic";
      topic: DocsDocumentationTopic;
    }
  | {
      id: string;
      title: string;
      kind: "topic-list";
      topics: DocsDocumentationTopic[];
      emptyText: string;
    }
  | {
      id: string;
      title: string;
      kind: "admin-reference";
    };

type DocsTreeNode = {
  id: string;
  title: string;
  page: DocsPageNode;
  children: DocsTreeNode[];
};

type OutlineItem = {
  id: string;
  label: string;
};

export default function DocsPage({ settings }: { settings: ApiSettings }) {
  const location = useLocation();
  const navigate = useGuardedNavigate();
  const { language } = usePlatformLanguage();
  const [context, setContext] = useState<DocsContext | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState("");
  const [documentationType, setDocumentationType] = useState<DocumentationType>(() => documentationTypeFromSearch(location.search));
  const [expandedNodes, setExpandedNodes] = useState<Set<string>>(() => new Set());
  const locale = localeFromSearch(location.search) ?? language;
  const adminDocs = documentationType === "admin";

  async function load() {
    setLoading(true);
    setError("");
    try {
      setContext(await fetchDocsContext(settings, { documentationType, locale }));
    } catch (err) {
      setError(adminErrorMessage(err));
    } finally {
      setLoading(false);
    }
  }

  useEffect(() => {
    const nextType = documentationTypeFromSearch(location.search);
    setDocumentationType((current) => current === nextType ? current : nextType);
  }, [location.search]);

  useEffect(() => { void load(); }, [settings.apiBaseUrl, settings.apiKey, settings.accessToken, documentationType, locale]);

  const treeNodes = useMemo(() => docsTreeNodes(context, adminDocs), [context, adminDocs]);
  const pages = useMemo(() => flattenTreePages(treeNodes), [treeNodes]);
  const selectedPage = selectedPageFromSearch(location.search, pages);
  const topicById = useMemo(() => topicIndex(context), [context]);
  const visibleRoutes = context?.layers.configured.routes ?? [];
  const availableRoutes = context?.layers.available.routes ?? [];
  const grantedPermissions = context?.layers.configured.permissions.filter((item) => item.granted) ?? [];
  const moduleRows = useMemo(() => context?.layers.configured.modules ?? [], [context]);
  const outlineItems = outlineForPage(selectedPage, adminDocs);

  useEffect(() => {
    setExpandedNodes(new Set(defaultExpandedNodeIds(treeNodes)));
  }, [treeNodes]);

  useEffect(() => {
    if (!selectedPage) return;
    const ancestorIds = ancestorNodeIdsForPage(treeNodes, selectedPage.id);
    if (!ancestorIds.length) return;
    setExpandedNodes((current) => {
      const next = new Set(current);
      for (const nodeId of ancestorIds) {
        next.add(nodeId);
      }
      return next;
    });
  }, [treeNodes, selectedPage?.id]);

  return (
    <div className="workspace module-workspace docs-workspace">
      <aside className="section-sidebar docs-outline" aria-label="i18n:govoplan-docs.documentation_outline.6f836b99">
        <div className="docs-sidebar-header">
          <div className="section-title">i18n:govoplan-docs.help_center.f3f3a34b</div>
          <AudienceToggle
            selected={documentationType}
            onSelect={selectDocumentationType}
            canViewAdmin={context?.actor.available_documentation_types.includes("admin") ?? documentationType === "admin"}
          />
        </div>
        <nav className="docs-tree" aria-label="i18n:govoplan-docs.documentation_outline.6f836b99">
          <ExplorerTree
            nodes={treeNodes}
            getNodeId={(node) => node.id}
            getNodeLabel={(node) => node.title}
            getNodeChildren={(node) => node.children}
            activeId={activeNodeIdForPage(treeNodes, selectedPage?.id ?? "")}
            expandedIds={expandedNodes}
            depth={0}
            childrenBaseClassName="docs-tree-children"
            nodeContainerClassName="docs-tree-node"
            nodeWrapBaseClassName="docs-tree-row"
            toggleBaseClassName="docs-tree-toggle"
            nodeButtonBaseClassName="docs-tree-page"
            getNodeWrapStyle={(_node, context) => ({ paddingLeft: `${context.depth * 14}px` })}
            getNodeButtonClassName={(node) => selectedPage?.id === node.page.id ? "is-active" : ""}
            renderToggleIcon={(_node, context) => context.hasChildren ? context.expanded ? <ChevronDown size={15} /> : <ChevronRight size={15} /> : <span className="docs-tree-toggle-placeholder" />}
            renderNodeContent={(node) => node.title}
            onToggle={(node) => toggleNode(node.id)}
            onOpen={(node) => selectPage(node.page)}
          />
        </nav>
      </aside>
      <section className="workspace-content docs-workspace-content">
        <div className="content-pad workspace-data-page docs-page">
          <div className="page-heading split workspace-heading">
            <div>
              <PageTitle loading={loading}>i18n:govoplan-docs.help_center.f3f3a34b</PageTitle>
              <p>{adminDocs ? "i18n:govoplan-docs.technical_documentation_for_the_modules_and_conf.267e739e" : "i18n:govoplan-docs.guidance_for_the_functions_available_in_this_ins.723b7cad"}</p>
            </div>
            <div className="button-row compact-actions">
              <Button onClick={() => void load()} disabled={loading}><RefreshCw size={16} /> i18n:govoplan-docs.reload.cce71553</Button>
            </div>
          </div>

          {error && <DismissibleAlert tone="danger" resetKey={error} floating>{error}</DismissibleAlert>}

          <LoadingFrame loading={loading} label="i18n:govoplan-docs.loading_documentation_context.1c091645">
            <div className="docs-content">
              <main className="docs-page-main">
                <SelectedPageContent
                  page={selectedPage}
                  adminDocs={adminDocs}
                  documentationType={documentationType}
                  topicById={topicById}
                  moduleRows={moduleRows}
                  visibleRoutes={visibleRoutes}
                  availableRoutes={availableRoutes}
                  grantedPermissions={grantedPermissions}
                  evidenceModules={context?.layers.evidence.optional_modules ?? []}
                  evidenceSources={context?.layers.evidence.sources ?? []}
                />
              </main>
              <PageOutline items={outlineItems} />
            </div>
          </LoadingFrame>
        </div>
      </section>
    </div>
  );

  function selectDocumentationType(type: DocumentationType) {
    const params = new URLSearchParams(location.search);
    params.set("type", type);
    navigate(`${location.pathname}?${params.toString()}`, { replace: true });
  }

  function selectPage(page: DocsPageNode) {
    const params = new URLSearchParams(location.search);
    params.set("topic", page.id);
    navigate(`${location.pathname}?${params.toString()}`, { replace: true });
  }

  function toggleNode(nodeId: string) {
    setExpandedNodes((current) => {
      const next = new Set(current);
      if (next.has(nodeId)) {
        next.delete(nodeId);
      } else {
        next.add(nodeId);
      }
      return next;
    });
  }
}

function AudienceToggle({ selected, onSelect, canViewAdmin }: { selected: DocumentationType; onSelect: (type: DocumentationType) => void; canViewAdmin: boolean }) {
  const options: Array<{ id: DocumentationType; label: string }> = [
    { id: "user", label: "i18n:govoplan-docs.user_docs.1e38e8d3" }
  ];
  if (canViewAdmin) {
    options.unshift({ id: "admin", label: "i18n:govoplan-docs.admin_docs.bf504a56" });
  }
  return (
    <SegmentedControl
      className="docs-audience-toggle"
      role="group"
      size="equal"
      width="fill"
      ariaLabel="i18n:govoplan-docs.documentation_type.5a66690c"
      value={selected}
      onChange={onSelect}
      options={options}
    />
  );
}

function PageOutline({ items }: { items: OutlineItem[] }) {
  return (
    <aside className="docs-page-outline-box" aria-label="i18n:govoplan-docs.page_outline.e5940949">
      <h2>i18n:govoplan-docs.page_outline.e5940949</h2>
      {items.length ?
        <nav>
          {items.map((item) => <a key={item.id} href={`#${item.id}`}>{item.label}</a>)}
        </nav> :
        <p className="muted">i18n:govoplan-docs.no_outline_items.3ea2842e</p>
      }
    </aside>
  );
}

function SelectedPageContent({
  page,
  adminDocs,
  documentationType,
  topicById,
  moduleRows,
  visibleRoutes,
  availableRoutes,
  grantedPermissions,
  evidenceModules,
  evidenceSources
}: {
  page: DocsPageNode | null;
  adminDocs: boolean;
  documentationType: DocumentationType;
  topicById: Map<string, DocsDocumentationTopic>;
  moduleRows: DocsModule[];
  visibleRoutes: DocsRoute[];
  availableRoutes: DocsRoute[];
  grantedPermissions: Array<{ scope: string; label: string; category: string }>;
  evidenceModules: DocsOptionalModuleEvidence[];
  evidenceSources: DocsSource[];
}) {
  if (!page) {
    return (
      <section className="docs-section">
        <h2>i18n:govoplan-docs.help_center.f3f3a34b</h2>
        <p className="muted">i18n:govoplan-docs.no_system_topics_found.d01a8068</p>
      </section>
    );
  }
  if (page.kind === "topic") {
    return <TopicPage topic={page.topic} showTechnical={adminDocs} documentationType={documentationType} topicById={topicById} />;
  }
  if (page.kind === "admin-reference") {
    return (
      <section className="docs-section" id={page.id}>
        <h2>{page.title}</h2>
        <section id="docs-admin-modules" className="docs-reference-block">
          <h3>i18n:govoplan-docs.modules.04e9462c</h3>
          <ModuleTable modules={moduleRows} />
        </section>
        <section id="docs-admin-routes" className="docs-reference-block">
          <h3>i18n:govoplan-docs.visible_routes.6c9b1605</h3>
          <RouteTable routes={visibleRoutes} emptyText="i18n:govoplan-docs.no_visible_routes_found_for_this_actor.3feeaf0b" />
          <h3>i18n:govoplan-docs.available_routes.c2635868</h3>
          <RouteTable routes={availableRoutes} emptyText="i18n:govoplan-docs.no_hidden_installed_routes_found.22f62845" />
        </section>
        <section id="docs-admin-permissions" className="docs-reference-block">
          <h3>i18n:govoplan-docs.granted_permissions.0a232e78</h3>
          <PermissionList permissions={grantedPermissions} />
          <EvidenceList modules={evidenceModules} sources={evidenceSources} />
        </section>
      </section>
    );
  }
  return (
    <section className="docs-section" id={page.id}>
      <h2>{page.title}</h2>
      <DocumentationTopicList topics={page.topics} emptyText={page.emptyText} showTechnical={adminDocs} documentationType={documentationType} topicById={topicById} />
    </section>
  );
}

function TopicPage({ topic, showTechnical, documentationType, topicById }: { topic: DocsDocumentationTopic; showTechnical: boolean; documentationType: DocumentationType; topicById: Map<string, DocsDocumentationTopic> }) {
  return (
    <article className={`docs-topic docs-topic-page docs-topic-${topic.kind}`} id={topicAnchorId(topic)}>
      <div className="docs-topic-meta">
        <StatusBadge status={topic.active ? "success" : "warning"} label={topic.active ? topic.kind : topic.target_layer} />
        <span className="muted">{topic.source_module_id}</span>
        {showTechnical && <span className="muted">{topic.i18n_key} · {topic.translation_locale}</span>}
      </div>
      <h2>{topic.title}</h2>
      {topic.summary && <p id={`${topicAnchorId(topic)}-summary`} className="docs-topic-summary">{topic.summary}</p>}
      <div id={`${topicAnchorId(topic)}-details`}>
        <BodyText value={topic.body} />
        <TopicMetadata topic={topic} showTechnical={showTechnical} documentationType={documentationType} topicById={topicById} />
        {!topic.active && <UnavailableDocumentationReason topic={topic} />}
      </div>
      {!!topic.unlocks.length && <p className="muted">i18n:govoplan-docs.unlocks.67d569db {topic.unlocks.join(" ")}</p>}
      {!!topic.links.length && <TopicLinks topic={topic} />}
    </article>
  );
}

function DocumentationTopicList({ topics, emptyText, showTechnical = false, documentationType, topicById }: { topics: DocsDocumentationTopic[]; emptyText: string; showTechnical?: boolean; documentationType: DocumentationType; topicById: Map<string, DocsDocumentationTopic> }) {
  if (!topics.length) return <p className="muted">{emptyText}</p>;
  return (
    <div className="docs-topic-list">
      {topics.map((topic) =>
        <article className={`docs-topic docs-topic-${topic.kind}`} id={topicAnchorId(topic)} key={`${topic.source_module_id}-${topic.id}`}>
          <div className="docs-topic-meta">
            <StatusBadge status={topic.active ? "success" : "warning"} label={topic.active ? topic.kind : topic.target_layer} />
            <span className="muted">{topic.source_module_id}</span>
            {showTechnical && <span className="muted">{topic.i18n_key} · {topic.translation_locale}</span>}
          </div>
          <h3>{topic.title}</h3>
          {topic.summary && <p className="docs-topic-summary">{topic.summary}</p>}
          <BodyText value={topic.body} />
          <TopicMetadata topic={topic} showTechnical={showTechnical} documentationType={documentationType} topicById={topicById} />
          {!topic.active && <UnavailableDocumentationReason topic={topic} />}
          {!!topic.unlocks.length && <p className="muted">i18n:govoplan-docs.unlocks.67d569db {topic.unlocks.join(" ")}</p>}
          {!!topic.links.length && <TopicLinks topic={topic} />}
        </article>
      )}
    </div>
  );
}

function TopicMetadata({ topic, showTechnical, documentationType, topicById }: { topic: DocsDocumentationTopic; showTechnical: boolean; documentationType: DocumentationType; topicById: Map<string, DocsDocumentationTopic> }) {
  const configuredDetails = <ConfiguredDetails topic={topic} />;
  if (topic.kind === "workflow") return <>{configuredDetails}<WorkflowDetails topic={topic} documentationType={documentationType} topicById={topicById} /></>;
  if (topic.kind === "reference") return <>{configuredDetails}<ReferenceDetails topic={topic} showTechnical={showTechnical} documentationType={documentationType} topicById={topicById} /></>;
  if (topic.kind === "pattern") return <>{configuredDetails}<PatternDetails topic={topic} showTechnical={showTechnical} documentationType={documentationType} topicById={topicById} /></>;
  return <>{configuredDetails}<RelatedTopics topic={topic} documentationType={documentationType} topicById={topicById} /></>;
}

function ConfiguredDetails({ topic }: { topic: DocsDocumentationTopic }) {
  const currentConfiguration = metadataList(topic.metadata, "current_configuration");
  const limitations = metadataList(topic.metadata, "limitations");
  const constraints = metadataRecords(topic.metadata, "constraints");
  const prefix = topicAnchorId(topic);
  if (!currentConfiguration.length && !limitations.length && !constraints.length) return null;
  return (
    <div className="docs-topic-details">
      {!!currentConfiguration.length && <DetailList id={`${prefix}-current-configuration`} title="i18n:govoplan-docs.this_system.b13a51ad" items={currentConfiguration} />}
      {!!constraints.length && <ConstraintDetails id={`${prefix}-constraints`} constraints={constraints} />}
      {!!limitations.length && <DetailList id={`${prefix}-limitations`} title="i18n:govoplan-docs.details.a6b3c45f" items={limitations} />}
    </div>
  );
}

function WorkflowDetails({ topic, documentationType, topicById }: { topic: DocsDocumentationTopic; documentationType: DocumentationType; topicById: Map<string, DocsDocumentationTopic> }) {
  const prerequisites = metadataList(topic.metadata, "prerequisites");
  const steps = metadataList(topic.metadata, "steps");
  const outcome = metadataString(topic.metadata, "outcome");
  const result = metadataString(topic.metadata, "result");
  const verification = metadataString(topic.metadata, "verification");
  const prefix = topicAnchorId(topic);
  return (
    <div className="docs-topic-details">
      {outcome && <DetailBlock id={`${prefix}-outcome`} title="i18n:govoplan-docs.outcome.10172bd3" value={outcome} />}
      {!!prerequisites.length && <DetailList id={`${prefix}-prerequisites`} title="i18n:govoplan-docs.prerequisites.fdf2407f" items={prerequisites} />}
      {!!steps.length &&
        <div className="docs-detail-block" id={`${prefix}-steps`}>
          <h4>i18n:govoplan-docs.steps.6041435e</h4>
          <ol>
            {steps.map((step) => <li key={step}>{step}</li>)}
          </ol>
        </div>
      }
      {result && <DetailBlock id={`${prefix}-result`} title="i18n:govoplan-docs.result.1f4fbf43" value={result} />}
      {verification && <DetailBlock id={`${prefix}-verification`} title="i18n:govoplan-docs.verification.4b5f10dd" value={verification} />}
      <RelatedTopics topic={topic} documentationType={documentationType} topicById={topicById} />
    </div>
  );
}

function ConstraintDetails({ id, constraints }: { id: string; constraints: Record<string, unknown>[] }) {
  return (
    <div className="docs-detail-block" id={id}>
      <h4>i18n:govoplan-docs.requirements.09a428f9</h4>
      <dl className="detail-list compact">
        {constraints.map((constraint, index) => {
          const label = metadataString(constraint, "label");
          const description = metadataString(constraint, "description");
          const values = metadataList(constraint, "values");
          return (
            <div key={metadataString(constraint, "id") || `${label}-${index}`}>
              <dt>{label}</dt>
              <dd>
                {description}
                {!!values.length && <span className="muted block">{values.join(", ")}</span>}
              </dd>
            </div>
          );
        })}
      </dl>
    </div>
  );
}

function ReferenceDetails({ topic, showTechnical, documentationType, topicById }: { topic: DocsDocumentationTopic; showTechnical: boolean; documentationType: DocumentationType; topicById: Map<string, DocsDocumentationTopic> }) {
  const route = metadataString(topic.metadata, "route");
  const screen = metadataString(topic.metadata, "screen");
  const section = metadataString(topic.metadata, "section");
  const fields = metadataRecords(topic.metadata, "fields");
  return (
    <div className="docs-topic-details">
      {(route || screen || section) &&
        <dl className="detail-list compact">
          {screen && <div><dt>i18n:govoplan-docs.screen.c4878ec4</dt><dd>{screen}</dd></div>}
          {section && <div><dt>i18n:govoplan-docs.section.5e498158</dt><dd>{section}</dd></div>}
          {showTechnical && route && <div><dt>i18n:govoplan-docs.route.4999528e</dt><dd>{route}</dd></div>}
        </dl>
      }
      {!!fields.length && <ReferenceFieldTable id={`${topicAnchorId(topic)}-fields`} fields={fields} showTechnical={showTechnical} />}
      <RelatedTopics topic={topic} documentationType={documentationType} topicById={topicById} />
    </div>
  );
}

function ReferenceFieldTable({ id, fields, showTechnical }: { id: string; fields: Record<string, unknown>[]; showTechnical: boolean }) {
  const columns: DataGridColumn<Record<string, unknown>>[] = [
    { id: "field", header: "i18n:govoplan-docs.field.7558c082", width: "minmax(180px, .8fr)", minWidth: 160, resizable: true, sortable: true, filterable: true, value: (field) => metadataString(field, "label"), render: (field) => <strong>{metadataString(field, "label")}</strong> },
    { id: "meaning", header: "i18n:govoplan-docs.meaning.584d8aa0", width: "minmax(260px, 1.3fr)", minWidth: 220, resizable: true, filterable: true, value: (field) => [metadataString(field, "user_description"), metadataString(field, "admin_description"), metadataString(field, "provenance")].join(" "), render: (field) => <div>{metadataString(field, "user_description")}{showTechnical && metadataString(field, "admin_description") && <span className="muted block">{metadataString(field, "admin_description")}</span>}{showTechnical && metadataString(field, "provenance") && <span className="muted block">{metadataString(field, "provenance")}</span>}</div> },
    ...(showTechnical ? [
      { id: "api", header: "i18n:govoplan-docs.api_mapping.e969f6f7", width: "minmax(190px, .8fr)", minWidth: 170, resizable: true, filterable: true, value: (field: Record<string, unknown>) => `${metadataString(field, "api_path")} ${metadataString(field, "api_field")}`, render: (field: Record<string, unknown>) => <div>{metadataString(field, "api_path")}<span className="muted block">{metadataString(field, "api_field")}</span></div> },
      { id: "permission", header: "i18n:govoplan-docs.permission.d71c9448", width: 190, filterable: true, value: (field: Record<string, unknown>) => metadataString(field, "permission_scope") || "-", render: (field: Record<string, unknown>) => metadataString(field, "permission_scope") || "-" }
    ] satisfies DataGridColumn<Record<string, unknown>>[] : []),
    { id: "validation", header: "i18n:govoplan-docs.validation.ef7f6d9c", width: "minmax(180px, .7fr)", minWidth: 160, resizable: true, filterable: true, value: (field) => metadataString(field, "validation") || "-", render: (field) => metadataString(field, "validation") || "-" }
  ];
  return (
    <div id={id}>
      <DataGrid id={`${id}-grid`} rows={fields} columns={columns} getRowKey={(field) => metadataString(field, "field_id") || metadataString(field, "label")} />
    </div>
  );
}

function PatternDetails({ topic, showTechnical, documentationType, topicById }: { topic: DocsDocumentationTopic; showTechnical: boolean; documentationType: DocumentationType; topicById: Map<string, DocsDocumentationTopic> }) {
  const purpose = metadataString(topic.metadata, "purpose");
  const whenUsed = metadataString(topic.metadata, "when_used");
  const explanation = showTechnical ? metadataString(topic.metadata, "admin_explanation") : metadataString(topic.metadata, "user_explanation");
  const componentRefs = metadataList(topic.metadata, "component_refs");
  const prefix = topicAnchorId(topic);
  return (
    <div className="docs-topic-details">
      {purpose && <DetailBlock id={`${prefix}-purpose`} title="i18n:govoplan-docs.purpose.4af40581" value={purpose} />}
      {whenUsed && <DetailBlock id={`${prefix}-when-used`} title="i18n:govoplan-docs.when_used.b870b361" value={whenUsed} />}
      {explanation && <DetailBlock id={`${prefix}-details`} title="i18n:govoplan-docs.details.a6b3c45f" value={explanation} />}
      {showTechnical && !!componentRefs.length && <DetailList id={`${prefix}-components`} title="i18n:govoplan-docs.components.e81e57d8" items={componentRefs} />}
      <RelatedTopics topic={topic} documentationType={documentationType} topicById={topicById} />
    </div>
  );
}

function DetailBlock({ id, title, value }: { id: string; title: string; value: string }) {
  return (
    <div className="docs-detail-block" id={id}>
      <h4>{title}</h4>
      <p>{value}</p>
    </div>
  );
}

function DetailList({ id, title, items }: { id: string; title: string; items: string[] }) {
  return (
    <div className="docs-detail-block" id={id}>
      <h4>{title}</h4>
      <ul>
        {items.map((item) => <li key={item}>{item}</li>)}
      </ul>
    </div>
  );
}

function RelatedTopics({ topic, documentationType, topicById }: { topic: DocsDocumentationTopic; documentationType: DocumentationType; topicById: Map<string, DocsDocumentationTopic> }) {
  const related = metadataList(topic.metadata, "related_topic_ids")
    .map((id) => topicById.get(id))
    .filter((item): item is DocsDocumentationTopic => Boolean(item));
  if (!related.length) return null;
  return (
    <p className="docs-topic-links">
      <span>i18n:govoplan-docs.related_topics.d5eb8b74 </span>
      {related.map((item, index) =>
        <span key={item.id}>
          {index > 0 && " | "}
          <Link to={topicPageHref(item, documentationType)}>{item.title}</Link>
        </span>
      )}
    </p>
  );
}

function TopicLinks({ topic }: { topic: DocsDocumentationTopic }) {
  return (
    <p className="docs-topic-links">
      {topic.links.map((link, index) =>
        <span key={link.href}>
          {index > 0 && " | "}
          <a href={link.href} target={link.kind === "public" ? "_blank" : undefined} rel={link.kind === "public" ? "noreferrer" : undefined}>{link.label}</a>
        </span>
      )}
    </p>
  );
}

function BodyText({ value }: { value: string }) {
  if (!value) return null;
  return (
    <>
      {value.split("\n").filter(Boolean).map((line) => <p className="docs-topic-body" key={line}>{line}</p>)}
    </>
  );
}

function UnavailableDocumentationReason({ topic }: { topic: DocsDocumentationTopic }) {
  const rows = [
    ["i18n:govoplan-docs.modules.04e9462c", topic.blockers.modules],
    ["i18n:govoplan-docs.capabilities.ca09c54b", topic.blockers.capabilities],
    ["i18n:govoplan-docs.permissions.d06d5557", topic.blockers.scopes]
  ].filter(([, values]) => Array.isArray(values) && values.length);
  return (
    <div className="docs-unavailable-reason">
      <p className="muted">{topic.reason}</p>
      {!!rows.length &&
        <dl className="detail-list compact">
          {rows.map(([label, values]) =>
            <div key={String(label)}>
              <dt>{label}</dt>
              <dd>{(values as string[]).join(", ")}</dd>
            </div>
          )}
        </dl>
      }
    </div>
  );
}

function docsTreeNodes(context: DocsContext | null, adminDocs: boolean): DocsTreeNode[] {
  const allTopics = allDocumentationTopics(context);
  const systemTopics = context?.topic_groups.system ?? [];
  const workflowTopics = context?.topic_groups.workflow ?? [];
  const referenceTopics = context?.topic_groups.reference ?? [];
  const patternTopics = context?.topic_groups.pattern ?? [];
  const systemTopicIds = new Set(systemTopics.map((topic) => topic.id));
  const moduleTopics = allTopics.filter((topic) => !systemTopicIds.has(topic.id));
  const workingTopics = uniqueTopics([...patternTopics, ...workflowTopics]);
  const troubleshootingTopics = allTopics.filter((topic) => !topic.active || topic.layer === "evidence");
  const basicsNode = categoryTreeNode(
    "basics",
    "i18n:govoplan-docs.basics.5fcebeef",
    patternTopics,
    "i18n:govoplan-docs.no_basic_topics_found.ee5ec3c6"
  );
  const commonTasksNode = categoryTreeNode(
    "common-tasks",
    "i18n:govoplan-docs.common_tasks.9f825c48",
    workflowTopics,
    "i18n:govoplan-docs.no_common_task_topics_found.c17814cf"
  );
  const nodes: DocsTreeNode[] = [
    categoryTreeNode(
      "about-govoplan",
      "i18n:govoplan-docs.about_govoplan.6b2d7127",
      systemTopics,
      "i18n:govoplan-docs.no_about_topics_found.7cd8af6d"
    ),
    {
      id: "tree:working-with-govoplan",
      title: "i18n:govoplan-docs.working_with_govoplan.81908ef4",
      page: {
        id: "working-with-govoplan:overview",
        title: "i18n:govoplan-docs.working_with_govoplan.81908ef4",
        kind: "topic-list",
        topics: workingTopics,
        emptyText: "i18n:govoplan-docs.no_working_with_govoplan_topics_found.b6db8384"
      },
      children: [basicsNode, commonTasksNode]
    },
    categoryTreeNode(
      "advanced",
      "i18n:govoplan-docs.advanced.203a8d6b",
      referenceTopics,
      "i18n:govoplan-docs.no_advanced_topics_found.271166c9"
    ),
    {
      id: "tree:modules",
      title: "i18n:govoplan-docs.modules.04e9462c",
      page: {
        id: "modules:overview",
        title: "i18n:govoplan-docs.modules.04e9462c",
        kind: "topic-list",
        topics: moduleTopics,
        emptyText: "i18n:govoplan-docs.no_module_topics_found.0cbbdc9b"
      },
      children: moduleTreeNodes(moduleTopics, context)
    }
  ];
  if (adminDocs) {
    nodes.push({
      id: "tree:administration",
      title: "i18n:govoplan-docs.administration.101aedaf",
      page: {
        id: "administration:overview",
        title: "i18n:govoplan-docs.administration.101aedaf",
        kind: "topic-list",
        topics: referenceTopics,
        emptyText: "i18n:govoplan-docs.no_administration_topics_found.6213dff2"
      },
      children: [
        ...referenceTopics.map(topicTreeNode),
        {
          id: "tree:administration:technical-reference",
          title: "i18n:govoplan-docs.technical_reference.f271430d",
          page: {
            id: "system:technical-reference",
            title: "i18n:govoplan-docs.technical_reference.f271430d",
            kind: "admin-reference"
          },
          children: []
        }
      ]
    });
  }
  if (troubleshootingTopics.length) {
    nodes.push(categoryTreeNode(
      "troubleshooting",
      "i18n:govoplan-docs.troubleshooting_and_support.3d42f434",
      troubleshootingTopics,
      "i18n:govoplan-docs.no_troubleshooting_topics_found.8c275468"
    ));
  }
  return nodes;
}

function categoryTreeNode(id: string, title: string, topics: DocsDocumentationTopic[], emptyText: string): DocsTreeNode {
  const sortedTopics = [...topics].sort((left, right) => left.order - right.order || left.title.localeCompare(right.title));
  return {
    id: `tree:${id}`,
    title,
    page: {
      id: `${id}:overview`,
      title,
      kind: "topic-list",
      topics: sortedTopics,
      emptyText
    },
    children: nestedTopicNodes(sortedTopics)
  };
}

function topicPage(topic: DocsDocumentationTopic): DocsPageNode {
  return {
    id: topic.id,
    title: topic.title,
    kind: "topic",
    topic
  };
}

function topicTreeNode(topic: DocsDocumentationTopic): DocsTreeNode {
  return {
    id: `tree:topic:${topic.id}`,
    title: topic.title,
    page: topicPage(topic),
    children: []
  };
}

function moduleTreeNodes(topics: DocsDocumentationTopic[], context: DocsContext | null): DocsTreeNode[] {
  const topicsByModule = new Map<string, DocsDocumentationTopic[]>();
  for (const topic of topics) {
    const moduleTopics = topicsByModule.get(topic.source_module_id) ?? [];
    moduleTopics.push(topic);
    topicsByModule.set(topic.source_module_id, moduleTopics);
  }
  const configuredModules = context?.layers.configured.modules ?? [];
  const moduleOrder = new Map(configuredModules.map((module, index) => [module.id, index]));
  return Array.from(topicsByModule.entries())
    .sort(([left], [right]) => (moduleOrder.get(left) ?? 999) - (moduleOrder.get(right) ?? 999) || moduleTitle(left, context).localeCompare(moduleTitle(right, context)))
    .map(([moduleId, moduleTopics]) => {
      const sortedTopics = [...moduleTopics].sort((left, right) => left.order - right.order || left.title.localeCompare(right.title));
      const title = moduleTitle(moduleId, context);
      return {
        id: `tree:module:${moduleId}`,
        title,
        page: {
          id: `module:${moduleId}`,
          title,
          kind: "topic-list",
          topics: sortedTopics,
          emptyText: "i18n:govoplan-docs.no_system_topics_found.d01a8068"
        },
        children: nestedTopicNodes(sortedTopics)
      };
    });
}

function nestedTopicNodes(topics: DocsDocumentationTopic[]): DocsTreeNode[] {
  const nodes = new Map(topics.map((topic) => [topic.id, topicTreeNode(topic)]));
  const roots: DocsTreeNode[] = [];
  for (const topic of topics) {
    const node = nodes.get(topic.id);
    if (!node) continue;
    const parentId = topicParentId(topic);
    const parent = parentId ? nodes.get(parentId) : undefined;
    if (parent && parentId !== topic.id) {
      parent.children.push(node);
    } else {
      roots.push(node);
    }
  }
  return roots;
}

function topicParentId(topic: DocsDocumentationTopic): string {
  return metadataString(topic.metadata, "tree_parent_id") || metadataString(topic.metadata, "parent_topic_id") || metadataString(topic.metadata, "parent_id");
}

function moduleTitle(moduleId: string, context: DocsContext | null): string {
  const module = context?.layers.configured.modules.find((item) => item.id === moduleId);
  if (module) return module.name;
  return moduleId
    .replace(/^govoplan[-_]/, "")
    .split(/[-_]+/)
    .filter(Boolean)
    .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
    .join(" ") || moduleId;
}

function flattenTreePages(nodes: DocsTreeNode[]): DocsPageNode[] {
  return nodes.flatMap((node) => [node.page, ...flattenTreePages(node.children)]);
}

function defaultExpandedNodeIds(nodes: DocsTreeNode[]): string[] {
  return nodes.filter((node) => node.children.length).map((node) => node.id);
}

function ancestorNodeIdsForPage(nodes: DocsTreeNode[], pageId: string, ancestors: string[] = []): string[] {
  for (const node of nodes) {
    if (node.page.id === pageId) return ancestors;
    const match = ancestorNodeIdsForPage(node.children, pageId, [...ancestors, node.id]);
    if (match.length) return match;
  }
  return [];
}

function activeNodeIdForPage(nodes: DocsTreeNode[], pageId: string): string {
  if (!pageId) return "";
  for (const node of nodes) {
    if (node.page.id === pageId) return node.id;
    const childId = activeNodeIdForPage(node.children, pageId);
    if (childId) return childId;
  }
  return "";
}

function allDocumentationTopics(context: DocsContext | null): DocsDocumentationTopic[] {
  const topics = Object.values(context?.topic_groups ?? {}).flat();
  return uniqueTopics(topics).sort((left, right) => left.order - right.order || left.title.localeCompare(right.title));
}

function uniqueTopics(topics: DocsDocumentationTopic[]): DocsDocumentationTopic[] {
  const seen = new Set<string>();
  return topics.filter((topic) => {
    if (seen.has(topic.id)) return false;
    seen.add(topic.id);
    return true;
  });
}

function selectedPageFromSearch(search: string, pages: DocsPageNode[]): DocsPageNode | null {
  if (!pages.length) return null;
  const params = new URLSearchParams(search);
  const requested = params.get("topic") || "";
  if (requested) return pages.find((page) => page.id === requested) ?? pages[0];
  const helpContext = params.get("context") || "";
  if (helpContext) {
    const exact = pages.find((page) => page.kind === "topic" && metadataList(page.topic.metadata, "help_contexts").includes(helpContext));
    if (exact) return exact;
    const moduleId = helpContextModuleId(helpContext);
    const moduleTopic = pages.find((page) => page.kind === "topic" && page.topic.source_module_id === moduleId);
    if (moduleTopic) return moduleTopic;
  }
  return pages[0];
}

function helpContextModuleId(value: string): string {
  const prefix = value.split(".", 1)[0];
  if (prefix === "campaign") return "campaigns";
  if (prefix === "address-book") return "addresses";
  return prefix;
}

function outlineForPage(page: DocsPageNode | null, showTechnical: boolean): OutlineItem[] {
  if (!page) return [];
  if (page.kind === "admin-reference") {
    return [
      { id: page.id, label: page.title },
      { id: "docs-admin-modules", label: "i18n:govoplan-docs.modules.04e9462c" },
      { id: "docs-admin-routes", label: "i18n:govoplan-docs.routes.03730e58" },
      { id: "docs-admin-permissions", label: "i18n:govoplan-docs.permissions.d06d5557" }
    ];
  }
  if (page.kind === "topic-list") {
    return [
      { id: page.id, label: page.title },
      ...page.topics.map((topic) => ({ id: topicAnchorId(topic), label: topic.title }))
    ];
  }
  const topic = page.topic;
  const prefix = topicAnchorId(topic);
  const items: OutlineItem[] = [{ id: prefix, label: topic.title }];
  if (topic.summary) items.push({ id: `${prefix}-summary`, label: "i18n:govoplan-docs.summary.d6b9936d" });
  if (metadataList(topic.metadata, "current_configuration").length) items.push({ id: `${prefix}-current-configuration`, label: "i18n:govoplan-docs.this_system.b13a51ad" });
  if (metadataRecords(topic.metadata, "constraints").length) items.push({ id: `${prefix}-constraints`, label: "i18n:govoplan-docs.requirements.09a428f9" });
  if (metadataList(topic.metadata, "limitations").length) items.push({ id: `${prefix}-limitations`, label: "i18n:govoplan-docs.details.a6b3c45f" });
  if (topic.kind === "workflow") {
    if (metadataString(topic.metadata, "outcome")) items.push({ id: `${prefix}-outcome`, label: "i18n:govoplan-docs.outcome.10172bd3" });
    if (metadataList(topic.metadata, "prerequisites").length) items.push({ id: `${prefix}-prerequisites`, label: "i18n:govoplan-docs.prerequisites.fdf2407f" });
    if (metadataList(topic.metadata, "steps").length) items.push({ id: `${prefix}-steps`, label: "i18n:govoplan-docs.steps.6041435e" });
    if (metadataString(topic.metadata, "result")) items.push({ id: `${prefix}-result`, label: "i18n:govoplan-docs.result.1f4fbf43" });
    if (metadataString(topic.metadata, "verification")) items.push({ id: `${prefix}-verification`, label: "i18n:govoplan-docs.verification.4b5f10dd" });
  }
  if (topic.kind === "reference" && metadataRecords(topic.metadata, "fields").length) {
    items.push({ id: `${prefix}-fields`, label: "i18n:govoplan-docs.field.7558c082" });
  }
  if (topic.kind === "pattern") {
    if (metadataString(topic.metadata, "purpose")) items.push({ id: `${prefix}-purpose`, label: "i18n:govoplan-docs.purpose.4af40581" });
    if (metadataString(topic.metadata, "when_used")) items.push({ id: `${prefix}-when-used`, label: "i18n:govoplan-docs.when_used.b870b361" });
    if (showTechnical && metadataList(topic.metadata, "component_refs").length) items.push({ id: `${prefix}-components`, label: "i18n:govoplan-docs.components.e81e57d8" });
  }
  return items;
}

function documentationTypeFromSearch(search: string): DocumentationType {
  return new URLSearchParams(search).get("type") === "admin" ? "admin" : "user";
}

function localeFromSearch(search: string): string | null {
  const value = new URLSearchParams(search).get("locale");
  if (!value) return null;
  return value.toLowerCase().startsWith("de") ? "de" : "en";
}

function topicAnchorId(topic: DocsDocumentationTopic): string {
  return topic.anchor_id || `docs-topic-${topic.source_module_id}-${topic.id}`.replace(/[^a-z0-9_-]+/gi, "-").toLowerCase();
}

function topicPageHref(topic: DocsDocumentationTopic, documentationType: DocumentationType): string {
  const params = new URLSearchParams();
  params.set("type", documentationType);
  params.set("topic", topic.id);
  return `?${params.toString()}#${topicAnchorId(topic)}`;
}

function topicIndex(context: DocsContext | null): Map<string, DocsDocumentationTopic> {
  const index = new Map<string, DocsDocumentationTopic>();
  for (const topic of allDocumentationTopics(context)) {
    index.set(topic.id, topic);
  }
  return index;
}

function metadataString(metadata: Record<string, unknown>, key: string): string {
  const value = metadata[key];
  return typeof value === "string" ? value : "";
}

function metadataList(metadata: Record<string, unknown>, key: string): string[] {
  const value = metadata[key];
  if (!Array.isArray(value)) return [];
  return value.filter((item): item is string => typeof item === "string");
}

function metadataRecords(metadata: Record<string, unknown>, key: string): Record<string, unknown>[] {
  const value = metadata[key];
  if (!Array.isArray(value)) return [];
  return value.filter((item): item is Record<string, unknown> => Boolean(item) && typeof item === "object" && !Array.isArray(item));
}

function ModuleTable({ modules }: { modules: DocsModule[] }) {
  if (!modules.length) return <p className="muted">i18n:govoplan-docs.no_configured_modules_found.f6f9ce24</p>;
  const columns: DataGridColumn<DocsModule>[] = [
    { id: "module", header: "i18n:govoplan-docs.module.b8ff0289", width: "minmax(220px, 1fr)", minWidth: 190, resizable: true, sortable: true, filterable: true, value: (module) => `${module.name} ${module.id} ${module.version}`, render: (module) => <div><strong>{module.name}</strong><span className="muted block">{module.id} {module.version}</span></div> },
    { id: "routes", header: "i18n:govoplan-docs.routes.03730e58", width: 170, sortable: true, value: (module) => module.route_count, render: (module) => <>{module.route_count} i18n:govoplan-docs.route.200e2a66 {module.nav_count} nav</> },
    { id: "permissions", header: "i18n:govoplan-docs.permissions.d06d5557", width: 130, sortable: true, value: (module) => module.permission_count },
    { id: "frontend", header: "i18n:govoplan-docs.frontend.152d1cf2", width: "minmax(180px, .8fr)", minWidth: 160, resizable: true, sortable: true, filterable: true, value: (module) => module.frontend_package || "-", render: (module) => module.frontend_package || "-" },
    { id: "capabilities", header: "i18n:govoplan-docs.capabilities.ca09c54b", width: "minmax(260px, 1.2fr)", minWidth: 220, resizable: true, filterable: true, value: (module) => module.capabilities.join(" "), render: (module) => <div>{module.capabilities.length ? module.capabilities.join(", ") : "-"}{module.documentation_count || module.documentation_provider_count ? <span className="muted block">{module.documentation_count} i18n:govoplan-docs.docs.5dc1e9b8 {module.documentation_provider_count} provider</span> : null}</div> }
  ];
  return (
    <DataGrid id="docs-configured-modules" rows={modules} columns={columns} getRowKey={(module) => module.id} />
  );
}

function RouteTable({ routes, emptyText }: { routes: DocsRoute[]; emptyText: string }) {
  if (!routes.length) return <p className="muted">{emptyText}</p>;
  const columns: DataGridColumn<DocsRoute>[] = [
    { id: "route", header: "i18n:govoplan-docs.route.4999528e", width: "minmax(240px, 1fr)", minWidth: 200, resizable: true, sortable: true, filterable: true, value: (route) => `${route.label} ${route.path}`, render: (route) => <div><strong>{route.label}</strong><span className="muted block">{route.path}</span></div> },
    { id: "module", header: "i18n:govoplan-docs.module.b8ff0289", width: 160, sortable: true, filterable: true, value: (route) => route.module_id },
    { id: "source", header: "i18n:govoplan-docs.source.6da13add", width: 160, sortable: true, filterable: true, value: (route) => route.source },
    { id: "requirements", header: "i18n:govoplan-docs.requirements.09a428f9", width: "minmax(220px, 1fr)", minWidth: 180, resizable: true, filterable: true, value: (route) => routeRequirements(route) },
    { id: "status", header: "i18n:govoplan-docs.status.bae7d5be", width: 160, sortable: true, filterable: true, value: (route) => route.visible ? "visible" : route.reason, render: (route) => <StatusBadge status={route.visible ? "success" : "warning"} label={route.visible ? "visible" : route.reason} /> }
  ];
  return (
    <DataGrid id={`docs-routes-${emptyText}`} rows={routes} columns={columns} getRowKey={(route) => `${route.source}-${route.module_id}-${route.path}`} />
  );
}

function PermissionList({ permissions }: { permissions: Array<{ scope: string; label: string; category: string }> }) {
  if (!permissions.length) return <p className="muted">i18n:govoplan-docs.no_granted_platform_permissions_found.36010898</p>;
  return (
    <dl className="detail-list">
      {permissions.slice(0, 24).map((permission) =>
        <div key={permission.scope}>
          <dt>{permission.category}</dt>
          <dd><strong>{permission.label}</strong><span className="muted"> · {permission.scope}</span></dd>
        </div>
      )}
      {permissions.length > 24 && <div><dt>i18n:govoplan-docs.more.4bab2d8f</dt><dd>{permissions.length - 24} i18n:govoplan-docs.additional_permissions.8042cb01</dd></div>}
    </dl>
  );
}

function EvidenceList({ modules, sources }: { modules: DocsOptionalModuleEvidence[]; sources: DocsSource[] }) {
  if (!modules.length && !sources.length) return <p className="muted">i18n:govoplan-docs.no_evidence_sources_found.be3bb2f6</p>;
  return (
    <dl className="detail-list">
      {modules.map((item) =>
        <div key={`${item.source_module_id}-${item.module_id}`}>
          <dt><StatusBadge status={item.status === "installed" ? "success" : "inactive"} label={item.status} /></dt>
          <dd><strong>{item.module_id}</strong><span className="muted"> · {item.reason}</span></dd>
        </div>
      )}
      {sources.map((item) =>
        <div key={item.source}>
          <dt>{item.layer}</dt>
          <dd><strong>{item.label}</strong><span className="muted"> · {item.source}</span></dd>
        </div>
      )}
    </dl>
  );
}

function routeRequirements(route: DocsRoute): string {
  const parts = [];
  if (route.required_all.length) parts.push(`all: ${route.required_all.join(", ")}`);
  if (route.required_any.length) parts.push(`any: ${route.required_any.join(", ")}`);
  return parts.join(" | ") || "-";
}
