import { createComponent as _$createComponent } from "opentui:runtime-module:%40opentui%2Fsolid";
import { memo as _$memo } from "opentui:runtime-module:%40opentui%2Fsolid";
import { createTextNode as _$createTextNode } from "opentui:runtime-module:%40opentui%2Fsolid";
import { effect as _$effect } from "opentui:runtime-module:%40opentui%2Fsolid";
import { insertNode as _$insertNode } from "opentui:runtime-module:%40opentui%2Fsolid";
import { insert as _$insert } from "opentui:runtime-module:%40opentui%2Fsolid";
import { setProp as _$setProp } from "opentui:runtime-module:%40opentui%2Fsolid";
import { createElement as _$createElement } from "opentui:runtime-module:%40opentui%2Fsolid";
/** @jsxImportSource @opentui/solid */
// @ts-nocheck

import { createMemo, createSignal, onCleanup } from "opentui:runtime-module:solid-js";
import { version as packageVersion } from "../../package.json";
import { AftRpcClient } from "../shared/rpc-client";
import { coerceAftStatus, formatBytes, formatCacheRoleLabel, formatSemanticIndexStatus, formatSemanticRefreshing } from "../shared/status";
import { createDebouncedStatusRefresh, refreshAftTuiSocketScope, startAftTuiSocket, stopAftTuiSocket, subscribeStatusInvalidations } from "./notification-socket";
import { createAftSidebarSlot, formatCompressionSidebarRows, isSnapshotForContext, resolveTuiStorageDir, shouldSuppressUninitializedDowngrade } from "./sidebar";

// The TUI talks to the server plugin via AftRpcClient. The client reads the
// JSON port file written by AftRpcServer ({ port, token }) and includes that
// per-server token on every RPC request; legacy integer port files are still
// tolerated for already-running older server plugins.

// RPC clients keyed by directory — one per project
const rpcClients = new Map();
function getRpcClient(directory) {
  let client = rpcClients.get(directory);
  if (client) return client;
  client = new AftRpcClient(resolveTuiStorageDir(), directory);
  rpcClients.set(directory, client);
  return client;
}
function getSessionId(api) {
  try {
    const route = api.route.current;
    if (route?.name === "session" && route.params?.sessionID) {
      return route.params.sessionID;
    }
  } catch {
    // ignore
  }
  return null;
}

// ---------------------------------------------------------------------------
// StatusDialog — themed, two-column JSX dialog. OpenCode renders DialogAlert
// text in a proportional font with no column alignment, so the status view uses
// TUI flex primitives instead of a padded monospace string. The component
// subscribes to server-pushed invalidations so it can re-render as the status
// snapshot changes, with no parent re-mount.
// ---------------------------------------------------------------------------

function formatCountShort(value) {
  if (value == null || !Number.isFinite(value)) return "—";
  if (value >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}M`;
  if (value >= 1_000) return `${Math.round(value / 1_000)}K`;
  return String(value);
}
function statusTone(status) {
  switch (status) {
    case "ready":
      return "ok";
    case "loading":
    case "building":
      return "warn";
    case "failed":
    case "error":
      return "err";
    default:
      return "muted";
  }
}
function pickToneColor(theme, tone) {
  switch (tone) {
    case "ok":
      return theme.success ?? theme.accent;
    case "warn":
      return theme.warning;
    case "err":
      return theme.error;
    case "muted":
      return theme.textMuted;
  }
}

/**
 * Label/value row. Label is left-aligned and muted; value is right-aligned
 * and themed. flexDirection="row" + justifyContent="space-between" replaces
 * the monospace `padEnd(40)` hack from the previous string formatter.
 */
const R = props => {
  const fg = createMemo(() => {
    if (!props.tone) return props.theme.text;
    if (props.tone === "accent") return props.theme.accent;
    return pickToneColor(props.theme, props.tone);
  });
  return (() => {
    var _el$ = _$createElement("box"),
      _el$2 = _$createElement("text"),
      _el$3 = _$createElement("text");
    _$insertNode(_el$, _el$2);
    _$insertNode(_el$, _el$3);
    _$setProp(_el$, "flexDirection", "row");
    _$setProp(_el$, "width", "100%");
    _$setProp(_el$, "justifyContent", "space-between");
    _$insert(_el$2, () => props.label);
    _$insert(_el$3, () => props.value);
    _$effect(_p$ => {
      var _v$ = props.theme.textMuted,
        _v$2 = fg();
      _v$ !== _p$.e && (_p$.e = _$setProp(_el$2, "fg", _v$, _p$.e));
      _v$2 !== _p$.t && (_p$.t = _$setProp(_el$3, "fg", _v$2, _p$.t));
      return _p$;
    }, {
      e: undefined,
      t: undefined
    });
    return _el$;
  })();
};
/**
 * Shared accept-gate for status RPC calls: skip warm responses that describe
 * another project (cross-project contamination from multi-project hosts —
 * see isSnapshotForContext) so they can't beat the right server's response.
 */
function statusAcceptGate(directory) {
  return result => {
    const rec = result;
    if (rec?.success === false) return true; // error envelopes handled by callers
    return isSnapshotForContext(coerceAftStatus(rec), directory, rec?.served_directory);
  };
}
const StatusDialog = props => {
  const theme = createMemo(() => props.api.theme.current);
  const t = () => theme();

  // Reactive status signal — the dialog re-renders on pushed status
  // invalidations without remounting.
  const [status, setStatus] = createSignal(props.initial);
  const [error, setError] = createSignal(props.initialError);
  let refreshGeneration = 0;
  let refreshController = null;
  const refreshStatus = async () => {
    if (refreshController) return;
    const controller = new AbortController();
    const requestGeneration = ++refreshGeneration;
    refreshController = controller;
    try {
      const response = await props.client.call("status", {
        sessionID: props.sessionID
      }, {
        signal: controller.signal,
        accept: statusAcceptGate(props.directory)
      });
      if (controller.signal.aborted || requestGeneration !== refreshGeneration) return;
      if (response.success !== false) {
        const snapshot = coerceAftStatus(response);
        // Stale-while-revalidate: don't downgrade a good snapshot to a transient
        // `not_initialized` (for example, while the bridge process respawns or
        // a session directory lookup briefly misses) — it arrives as success:true
        // and would blank the dialog until the next refresh.
        const current = status();
        if (shouldSuppressUninitializedDowngrade(snapshot.cache_role, current !== null && current.cache_role !== "not_initialized")) {
          return;
        }
        setStatus(snapshot);
        setError(null);
      }
    } catch {
      if (controller.signal.aborted || requestGeneration !== refreshGeneration) return;
      // transient — keep showing last good snapshot
    } finally {
      if (refreshController === controller) refreshController = null;
    }
  };
  const statusDebouncer = createDebouncedStatusRefresh(refreshStatus, 200);
  const unsubscribeStatusInvalidations = subscribeStatusInvalidations(event => {
    if (event.sessionId && event.sessionId !== props.sessionID) return;
    statusDebouncer.schedule();
  });
  onCleanup(() => {
    unsubscribeStatusInvalidations();
    statusDebouncer.dispose();
    refreshGeneration++;
    if (refreshController) {
      refreshController.abort();
      refreshController = null;
    }
  });

  // Visual cache-role badge: main is accent. Worktree/read_only borrow the
  // shared repo index — muted, not warning, so it is not read as a failure.
  const cacheRoleTone = role => role === "main" ? "accent" : "muted";
  // Reuse the sidebar's label/value formatter so the dialog and sidebar
  // render identical text (e.g. "Session" / "-174,489 tokens, 59% reduction").
  // The earlier `formatCompressionDialogRows` returned padded strings that
  // looked offset against neighboring sections.
  const compressionAggregateRows = () => formatCompressionSidebarRows(status()?.compression);
  return (() => {
    var _el$4 = _$createElement("box"),
      _el$5 = _$createElement("box"),
      _el$6 = _$createElement("text"),
      _el$7 = _$createElement("b"),
      _el$9 = _$createElement("box"),
      _el$0 = _$createElement("text");
    _$insertNode(_el$4, _el$5);
    _$insertNode(_el$4, _el$9);
    _$setProp(_el$4, "flexDirection", "column");
    _$setProp(_el$4, "width", "100%");
    _$setProp(_el$4, "paddingLeft", 2);
    _$setProp(_el$4, "paddingRight", 2);
    _$setProp(_el$4, "paddingTop", 1);
    _$setProp(_el$4, "paddingBottom", 1);
    _$insertNode(_el$5, _el$6);
    _$setProp(_el$5, "justifyContent", "center");
    _$setProp(_el$5, "width", "100%");
    _$setProp(_el$5, "marginBottom", 1);
    _$setProp(_el$5, "flexDirection", "row");
    _$setProp(_el$5, "gap", 2);
    _$insertNode(_el$6, _el$7);
    _$insertNode(_el$7, _$createTextNode(`⚡ AFT Status`));
    _$insert(_el$5, (() => {
      var _c$ = _$memo(() => status()?.cache_role !== "not_initialized");
      return () => _c$() && (() => {
        var _el$10 = _$createElement("text"),
          _el$11 = _$createTextNode(`v`);
        _$insertNode(_el$10, _el$11);
        _$insert(_el$10, () => status()?.version ?? packageVersion, null);
        _$effect(_$p => _$setProp(_el$10, "fg", t().textMuted, _$p));
        return _el$10;
      })();
    })(), null);
    _$insert(_el$4, (() => {
      var _c$2 = _$memo(() => !!error());
      return () => _c$2() ? (() => {
        var _el$12 = _$createElement("box"),
          _el$13 = _$createElement("text");
        _$insertNode(_el$12, _el$13);
        _$setProp(_el$12, "width", "100%");
        _$setProp(_el$12, "marginBottom", 1);
        _$insert(_el$13, error);
        _$effect(_$p => _$setProp(_el$13, "fg", t().warning, _$p));
        return _el$12;
      })() : null;
    })(), _el$9);
    _$insert(_el$4, (() => {
      var _c$3 = _$memo(() => status()?.cache_role === "not_initialized");
      return () => _c$3() ? (() => {
        var _el$14 = _$createElement("box"),
          _el$15 = _$createElement("text");
        _$insertNode(_el$14, _el$15);
        _$setProp(_el$14, "width", "100%");
        _$setProp(_el$14, "marginTop", 1);
        _$setProp(_el$14, "justifyContent", "center");
        _$insert(_el$15, () => status().message || "AFT bridge is now spawned lazily, information here will be populated after first tool call.");
        _$effect(_$p => _$setProp(_el$15, "fg", t().textMuted, _$p));
        return _el$14;
      })() : null;
    })(), _el$9);
    _$insert(_el$4, (() => {
      var _c$4 = _$memo(() => !!(status() && status().cache_role !== "not_initialized"));
      return () => _c$4() ? (() => {
        var _el$16 = _$createElement("box");
        _$setProp(_el$16, "flexDirection", "column");
        _$setProp(_el$16, "width", "100%");
        _$setProp(_el$16, "marginBottom", 1);
        _$insert(_el$16, _$createComponent(R, {
          get theme() {
            return t();
          },
          label: "Project root",
          get value() {
            return status().project_root ?? "(not configured)";
          }
        }), null);
        _$insert(_el$16, _$createComponent(R, {
          get theme() {
            return t();
          },
          label: "Canonical root",
          get value() {
            return status().canonical_root ?? "(not configured)";
          }
        }), null);
        _$insert(_el$16, _$createComponent(R, {
          get theme() {
            return t();
          },
          label: "Cache role",
          get value() {
            return formatCacheRoleLabel(status().cache_role);
          },
          get tone() {
            return cacheRoleTone(status().cache_role);
          }
        }), null);
        return _el$16;
      })() : null;
    })(), _el$9);
    _$insert(_el$4, (() => {
      var _c$5 = _$memo(() => !!(status() && status().cache_role !== "not_initialized"));
      return () => _c$5() ? (() => {
        var _el$17 = _$createElement("box"),
          _el$18 = _$createElement("box"),
          _el$19 = _$createElement("text"),
          _el$20 = _$createElement("b"),
          _el$22 = _$createElement("box"),
          _el$23 = _$createElement("text"),
          _el$24 = _$createElement("b"),
          _el$26 = _$createElement("box"),
          _el$27 = _$createElement("text"),
          _el$28 = _$createElement("b"),
          _el$30 = _$createElement("box"),
          _el$31 = _$createElement("text"),
          _el$32 = _$createElement("b"),
          _el$34 = _$createElement("box"),
          _el$35 = _$createElement("text"),
          _el$36 = _$createElement("b");
        _$insertNode(_el$17, _el$18);
        _$insertNode(_el$17, _el$30);
        _$setProp(_el$17, "flexDirection", "row");
        _$setProp(_el$17, "width", "100%");
        _$setProp(_el$17, "gap", 4);
        _$insertNode(_el$18, _el$19);
        _$insertNode(_el$18, _el$22);
        _$insertNode(_el$18, _el$26);
        _$setProp(_el$18, "flexDirection", "column");
        _$setProp(_el$18, "flexGrow", 1);
        _$setProp(_el$18, "flexBasis", 0);
        _$insertNode(_el$19, _el$20);
        _$insertNode(_el$20, _$createTextNode(`Search index`));
        _$insert(_el$18, _$createComponent(R, {
          get theme() {
            return t();
          },
          label: "Status",
          get value() {
            return status().search_index.status;
          },
          get tone() {
            return statusTone(status().search_index.status);
          }
        }), _el$22);
        _$insert(_el$18, _$createComponent(R, {
          get theme() {
            return t();
          },
          label: "Files",
          get value() {
            return formatCountShort(status().search_index.files);
          }
        }), _el$22);
        _$insert(_el$18, _$createComponent(R, {
          get theme() {
            return t();
          },
          label: "Trigrams",
          get value() {
            return formatCountShort(status().search_index.trigrams);
          }
        }), _el$22);
        _$insert(_el$18, _$createComponent(R, {
          get theme() {
            return t();
          },
          label: "Disk",
          get value() {
            return formatBytes(status().disk.trigram_disk_bytes);
          },
          tone: "muted"
        }), _el$22);
        _$insertNode(_el$22, _el$23);
        _$setProp(_el$22, "marginTop", 1);
        _$insertNode(_el$23, _el$24);
        _$insertNode(_el$24, _$createTextNode(`Runtime`));
        _$insert(_el$18, _$createComponent(R, {
          get theme() {
            return t();
          },
          label: "LSP servers",
          get value() {
            return String(status().lsp_servers);
          }
        }), _el$26);
        _$insert(_el$18, _$createComponent(R, {
          get theme() {
            return t();
          },
          label: "Symbol cache (local)",
          get value() {
            return formatCountShort(status().symbol_cache.local_entries);
          }
        }), _el$26);
        _$insert(_el$18, _$createComponent(R, {
          get theme() {
            return t();
          },
          label: "Symbol cache (warm)",
          get value() {
            return formatCountShort(status().symbol_cache.warm_entries);
          },
          tone: "muted"
        }), _el$26);
        _$insertNode(_el$26, _el$27);
        _$setProp(_el$26, "marginTop", 1);
        _$insertNode(_el$27, _el$28);
        _$insertNode(_el$28, _$createTextNode(`Features`));
        _$insert(_el$18, _$createComponent(R, {
          get theme() {
            return t();
          },
          label: "format_on_edit",
          get value() {
            return status().features.format_on_edit ? "on" : "off";
          },
          get tone() {
            return status().features.format_on_edit ? "ok" : "muted";
          }
        }), null);
        _$insert(_el$18, _$createComponent(R, {
          get theme() {
            return t();
          },
          label: "search_index",
          get value() {
            return status().features.search_index ? "on" : "off";
          },
          get tone() {
            return status().features.search_index ? "ok" : "muted";
          }
        }), null);
        _$insert(_el$18, _$createComponent(R, {
          get theme() {
            return t();
          },
          label: "semantic_search",
          get value() {
            return status().features.semantic_search ? "on" : "off";
          },
          get tone() {
            return status().features.semantic_search ? "ok" : "muted";
          }
        }), null);
        _$insertNode(_el$30, _el$31);
        _$insertNode(_el$30, _el$34);
        _$setProp(_el$30, "flexDirection", "column");
        _$setProp(_el$30, "flexGrow", 1);
        _$setProp(_el$30, "flexBasis", 0);
        _$insertNode(_el$31, _el$32);
        _$insertNode(_el$32, _$createTextNode(`Semantic index`));
        _$insert(_el$30, _$createComponent(R, {
          get theme() {
            return t();
          },
          label: "Status",
          get value() {
            return formatSemanticIndexStatus(status().semantic_index.status, status().semantic_index.stage);
          },
          get tone() {
            return statusTone(status().semantic_index.status);
          }
        }), _el$34);
        _$insert(_el$30, (() => {
          var _c$0 = _$memo(() => !!formatSemanticRefreshing(status().semantic_index.refreshing_count));
          return () => _c$0() ? (() => {
            var _el$38 = _$createElement("box"),
              _el$39 = _$createElement("text");
            _$insertNode(_el$38, _el$39);
            _$setProp(_el$38, "width", "100%");
            _$insert(_el$39, () => formatSemanticRefreshing(status().semantic_index.refreshing_count));
            _$effect(_$p => _$setProp(_el$39, "fg", t().textMuted, _$p));
            return _el$38;
          })() : null;
        })(), _el$34);
        _$insert(_el$30, _$createComponent(R, {
          get theme() {
            return t();
          },
          label: "Entries",
          get value() {
            return formatCountShort(status().semantic_index.entries);
          }
        }), _el$34);
        _$insert(_el$30, (() => {
          var _c$1 = _$memo(() => !!status().semantic_index.backend);
          return () => _c$1() ? _$createComponent(R, {
            get theme() {
              return t();
            },
            label: "Backend",
            get value() {
              return status().semantic_index.backend;
            },
            tone: "muted"
          }) : null;
        })(), _el$34);
        _$insert(_el$30, (() => {
          var _c$10 = _$memo(() => !!status().semantic_index.model);
          return () => _c$10() ? _$createComponent(R, {
            get theme() {
              return t();
            },
            label: "Model",
            get value() {
              return status().semantic_index.model;
            },
            tone: "muted"
          }) : null;
        })(), _el$34);
        _$insert(_el$30, (() => {
          var _c$11 = _$memo(() => status().semantic_index.dimension != null);
          return () => _c$11() ? _$createComponent(R, {
            get theme() {
              return t();
            },
            label: "Dimension",
            get value() {
              return String(status().semantic_index.dimension);
            },
            tone: "muted"
          }) : null;
        })(), _el$34);
        _$insert(_el$30, _$createComponent(R, {
          get theme() {
            return t();
          },
          label: "Disk",
          get value() {
            return formatBytes(status().disk.semantic_disk_bytes);
          },
          tone: "muted"
        }), _el$34);
        _$insertNode(_el$34, _el$35);
        _$setProp(_el$34, "marginTop", 1);
        _$insertNode(_el$35, _el$36);
        _$insertNode(_el$36, _$createTextNode(`Current session`));
        _$insert(_el$30, _$createComponent(R, {
          get theme() {
            return t();
          },
          label: "Tracked files",
          get value() {
            return String(status().session.tracked_files);
          }
        }), null);
        _$insert(_el$30, _$createComponent(R, {
          get theme() {
            return t();
          },
          label: "Checkpoints",
          get value() {
            return String(status().session.checkpoints);
          }
        }), null);
        _$insert(_el$30, _$createComponent(R, {
          get theme() {
            return t();
          },
          label: "All-session checkpoints",
          get value() {
            return String(status().checkpoints_total);
          },
          tone: "muted"
        }), null);
        _$effect(_p$ => {
          var _v$5 = t().text,
            _v$6 = t().text,
            _v$7 = t().text,
            _v$8 = t().text,
            _v$9 = t().text;
          _v$5 !== _p$.e && (_p$.e = _$setProp(_el$19, "fg", _v$5, _p$.e));
          _v$6 !== _p$.t && (_p$.t = _$setProp(_el$23, "fg", _v$6, _p$.t));
          _v$7 !== _p$.a && (_p$.a = _$setProp(_el$27, "fg", _v$7, _p$.a));
          _v$8 !== _p$.o && (_p$.o = _$setProp(_el$31, "fg", _v$8, _p$.o));
          _v$9 !== _p$.i && (_p$.i = _$setProp(_el$35, "fg", _v$9, _p$.i));
          return _p$;
        }, {
          e: undefined,
          t: undefined,
          a: undefined,
          o: undefined,
          i: undefined
        });
        return _el$17;
      })() : null;
    })(), _el$9);
    _$insert(_el$4, (() => {
      var _c$6 = _$memo(() => !!status()?.semantic_index.stage);
      return () => _c$6() ? (() => {
        var _el$40 = _$createElement("box"),
          _el$41 = _$createElement("text"),
          _el$42 = _$createElement("b");
        _$insertNode(_el$40, _el$41);
        _$setProp(_el$40, "flexDirection", "column");
        _$setProp(_el$40, "width", "100%");
        _$setProp(_el$40, "marginTop", 1);
        _$insertNode(_el$41, _el$42);
        _$insertNode(_el$42, _$createTextNode(`Semantic build progress`));
        _$insert(_el$40, _$createComponent(R, {
          get theme() {
            return t();
          },
          label: "Stage",
          get value() {
            return status().semantic_index.stage;
          }
        }), null);
        _$insert(_el$40, (() => {
          var _c$12 = _$memo(() => status().semantic_index.files != null);
          return () => _c$12() ? _$createComponent(R, {
            get theme() {
              return t();
            },
            label: "Files seen",
            get value() {
              return formatCountShort(status().semantic_index.files);
            }
          }) : null;
        })(), null);
        _$insert(_el$40, (() => {
          var _c$13 = _$memo(() => !!(status().semantic_index.entries_done != null || status().semantic_index.entries_total != null));
          return () => _c$13() ? _$createComponent(R, {
            get theme() {
              return t();
            },
            label: "Progress",
            get value() {
              return `${formatCountShort(status().semantic_index.entries_done ?? null)} / ${formatCountShort(status().semantic_index.entries_total ?? null)}`;
            }
          }) : null;
        })(), null);
        _$effect(_$p => _$setProp(_el$41, "fg", t().text, _$p));
        return _el$40;
      })() : null;
    })(), _el$9);
    _$insert(_el$4, (() => {
      var _c$7 = _$memo(() => !!status()?.semantic_index.error);
      return () => _c$7() ? (() => {
        var _el$44 = _$createElement("box"),
          _el$45 = _$createElement("text"),
          _el$46 = _$createTextNode(`⚠ `);
        _$insertNode(_el$44, _el$45);
        _$setProp(_el$44, "marginTop", 1);
        _$setProp(_el$44, "width", "100%");
        _$insertNode(_el$45, _el$46);
        _$insert(_el$45, () => status().semantic_index.error, null);
        _$effect(_$p => _$setProp(_el$45, "fg", t().error, _$p));
        return _el$44;
      })() : null;
    })(), _el$9);
    _$insert(_el$4, (() => {
      var _c$8 = _$memo(() => compressionAggregateRows().length > 0);
      return () => _c$8() ? (() => {
        var _el$47 = _$createElement("box"),
          _el$48 = _$createElement("text"),
          _el$49 = _$createElement("b");
        _$insertNode(_el$47, _el$48);
        _$setProp(_el$47, "flexDirection", "column");
        _$setProp(_el$47, "width", "100%");
        _$setProp(_el$47, "marginTop", 1);
        _$insertNode(_el$48, _el$49);
        _$insertNode(_el$49, _$createTextNode(`Compression`));
        _$insert(_el$47, () => compressionAggregateRows().map(row => row.kind === "scope" ? (() => {
          var _el$51 = _$createElement("box"),
            _el$52 = _$createElement("text");
          _$insertNode(_el$51, _el$52);
          _$setProp(_el$51, "width", "100%");
          _$insert(_el$52, () => row.label);
          _$effect(_$p => _$setProp(_el$52, "fg", t().text, _$p));
          return _el$51;
        })() : _$createComponent(R, {
          get theme() {
            return t();
          },
          get label() {
            return row.label;
          },
          get value() {
            return row.value;
          },
          tone: "muted"
        })), null);
        _$effect(_$p => _$setProp(_el$48, "fg", t().text, _$p));
        return _el$47;
      })() : null;
    })(), _el$9);
    _$insert(_el$4, (() => {
      var _c$9 = _$memo(() => !!status()?.status_bar);
      return () => _c$9() ? (() => {
        var _el$53 = _$createElement("box"),
          _el$54 = _$createElement("text"),
          _el$55 = _$createElement("b");
        _$insertNode(_el$53, _el$54);
        _$setProp(_el$53, "flexDirection", "column");
        _$setProp(_el$53, "width", "100%");
        _$setProp(_el$53, "marginTop", 1);
        _$insertNode(_el$54, _el$55);
        _$insert(_el$55, () => status().status_bar.tier2_stale ? "Code Health ~" : "Code Health");
        _$insert(_el$53, (() => {
          var _c$14 = _$memo(() => status().status_bar.errors !== undefined);
          return () => _c$14() ? _$createComponent(R, {
            get theme() {
              return t();
            },
            label: "Errors",
            get value() {
              return formatCountShort(status().status_bar.errors);
            },
            get tone() {
              return status().status_bar.errors > 0 ? "err" : "muted";
            }
          }) : null;
        })(), null);
        _$insert(_el$53, (() => {
          var _c$15 = _$memo(() => status().status_bar.warnings !== undefined);
          return () => _c$15() ? _$createComponent(R, {
            get theme() {
              return t();
            },
            label: "Warnings",
            get value() {
              return formatCountShort(status().status_bar.warnings);
            },
            get tone() {
              return status().status_bar.warnings > 0 ? "warn" : "muted";
            }
          }) : null;
        })(), null);
        _$insert(_el$53, (() => {
          var _c$16 = _$memo(() => status().status_bar.dead_code !== undefined);
          return () => _c$16() ? _$createComponent(R, {
            get theme() {
              return t();
            },
            label: "Dead Code",
            get value() {
              return formatCountShort(status().status_bar.dead_code);
            },
            tone: "muted"
          }) : null;
        })(), null);
        _$insert(_el$53, (() => {
          var _c$17 = _$memo(() => status().status_bar.unused_exports !== undefined);
          return () => _c$17() ? _$createComponent(R, {
            get theme() {
              return t();
            },
            label: "Unused Exports",
            get value() {
              return formatCountShort(status().status_bar.unused_exports);
            },
            tone: "muted"
          }) : null;
        })(), null);
        _$insert(_el$53, (() => {
          var _c$18 = _$memo(() => status().status_bar.duplicates !== undefined);
          return () => _c$18() ? _$createComponent(R, {
            get theme() {
              return t();
            },
            label: "Duplicates",
            get value() {
              return formatCountShort(status().status_bar.duplicates);
            },
            tone: "muted"
          }) : null;
        })(), null);
        _$insert(_el$53, (() => {
          var _c$19 = _$memo(() => status().status_bar.todos !== undefined);
          return () => _c$19() ? _$createComponent(R, {
            get theme() {
              return t();
            },
            label: "TODOs",
            get value() {
              return formatCountShort(status().status_bar.todos);
            },
            tone: "muted"
          }) : null;
        })(), null);
        _$effect(_$p => _$setProp(_el$54, "fg", t().text, _$p));
        return _el$53;
      })() : null;
    })(), _el$9);
    _$insertNode(_el$9, _el$0);
    _$setProp(_el$9, "marginTop", 1);
    _$setProp(_el$9, "justifyContent", "flex-end");
    _$setProp(_el$9, "width", "100%");
    _$insertNode(_el$0, _$createTextNode(`Enter or Esc to close`));
    _$effect(_p$ => {
      var _v$3 = t().accent,
        _v$4 = t().textMuted;
      _v$3 !== _p$.e && (_p$.e = _$setProp(_el$6, "fg", _v$3, _p$.e));
      _v$4 !== _p$.t && (_p$.t = _$setProp(_el$0, "fg", _v$4, _p$.t));
      return _p$;
    }, {
      e: undefined,
      t: undefined
    });
    return _el$4;
  })();
};
async function showStatusDialog(api) {
  const sessionID = getSessionId(api);
  if (!sessionID) {
    api.ui.toast({
      message: "No active session",
      variant: "warning",
      duration: 5000
    });
    return;
  }
  const directory = api.state.path.directory ?? "";
  if (!directory) {
    api.ui.toast({
      message: "No project directory",
      variant: "warning",
      duration: 5000
    });
    return;
  }
  const client = getRpcClient(directory);

  // Prime the dialog with one initial fetch so we don't show a blank
  // skeleton — the component then listens for pushed invalidations.
  let initial = null;
  let initialError = null;
  try {
    const response = await client.call("status", {
      sessionID
    }, {
      accept: statusAcceptGate(directory)
    });
    if (response.success !== false) {
      initial = coerceAftStatus(response);
    } else {
      initialError = "AFT bridge returned an error response.";
    }
  } catch {
    initialError = "AFT is starting up. Status will refresh automatically...";
  }

  // The dialog host already wraps every replace()'d element in its own centered
  // frame and binds Esc/Ctrl-C to close it. Rendering our own dialog frame inside
  // that host frame would misplace the content and focus; pass the bare component
  // so the host frames it once.
  // Do not pass an onClose that calls dialog.clear(). The dialog system calls
  // every entry's onClose on Esc, and clear() would re-trigger them, causing
  // infinite recursion. The host itself pops the dialog; StatusDialog cleans up
  // its own subscriptions in onCleanup. `replace` resets size to "medium", so
  // request "large" after the call.
  api.ui.dialog.replace(() => _$createComponent(StatusDialog, {
    api: api,
    client: client,
    directory: directory,
    sessionID: sessionID,
    initial: initial,
    initialError: initialError
  }));
  api.ui.dialog.setSize("large");
}

/**
 * Register the AFT status palette command, preferring the v1.14.42+ keymap API
 * and falling back to the legacy `api.command.register` for older hosts.
 *
 * No slash entry is registered here. The server config hook owns the single
 * `/aft-status` slash registration, while this palette command keeps Ctrl+P
 * discovery in the TUI.
 */
function registerStatusCommand(api) {
  const apiAny = api;
  if (typeof apiAny.keymap?.registerLayer === "function") {
    apiAny.keymap.registerLayer({
      commands: [{
        namespace: "palette",
        name: "aft.status",
        title: "AFT: Status",
        category: "AFT",
        run() {
          void showStatusDialog(api);
        }
      }],
      bindings: []
    });
    return;
  }
  if (typeof apiAny.command?.register === "function") {
    apiAny.command.register(() => [{
      title: "AFT: Status",
      value: "aft.status",
      category: "AFT",
      onSelect() {
        void showStatusDialog(api);
      }
    }]);
    return;
  }

  // Neither API surface is present. The TUI host can still load; users can
  // still see AFT status through the sidebar and server-owned slash command.
}
async function showStartupNotifications(api) {
  const directory = api.state.path.directory ?? "";
  if (!directory) return;
  const client = getRpcClient(directory);

  // Check for feature announcements
  try {
    const announcement = await client.call("get-announcement", {});
    if (announcement.show && announcement.version && announcement.features?.length) {
      const featureText = announcement.features.map(f => `  • ${f}`).join("\n");
      // Blank-line separator distinguishes the persistent footer (Discord
      // invite, etc.) from the version-specific bullets.
      const hasFooter = typeof announcement.footer === "string" && announcement.footer.trim().length > 0;
      const message = hasFooter ? `What's new:\n\n${featureText}\n\n${announcement.footer}` : `What's new:\n\n${featureText}`;
      api.ui.dialog.replace(() => _$createComponent(api.ui.DialogAlert, {
        get title() {
          return `AFT v${announcement.version}`;
        },
        message: message,
        onConfirm: () => {
          // Mark as announced so it doesn't show again
          void client.call("mark-announced", {});
        }
      }), () => {
        void client.call("mark-announced", {});
      });
      return; // Show one dialog at a time
    }
  } catch {
    // RPC server not ready yet — skip announcements
  }

  // Check for warnings
  try {
    const result = await client.call("get-warnings", {});
    if (result.warnings?.length) {
      const warningText = result.warnings.join("\n\n");
      api.ui.dialog.replace(() => _$createComponent(api.ui.DialogAlert, {
        title: "AFT Warning",
        message: warningText,
        onConfirm: () => {}
      }), () => {});
    }
  } catch {
    // RPC server not ready — skip warnings
  }
}
const tui = async api => {
  // Sidebar slot: live status of search index, semantic index, and disk
  // usage. See ./sidebar.tsx for the panel itself. Registered before the
  // command palette entry so the sidebar is available immediately when the
  // user opens their first session.
  try {
    api.slots.register(await createAftSidebarSlot(api, packageVersion));
  } catch {
    // Older OpenCode TUI hosts may not implement api.slots; fall through and
    // keep the palette command / server-owned slash command working.
  }

  // OpenCode 1.14.42 removed `api.command.register` entirely
  // (anomalyco/opencode PR #26053). Later patches reinstated it as a
  // deprecated shim that translates to `api.keymap.registerLayer`. To work
  // across the whole 1.14.x line — including the brief 1.14.42 / 1.14.43
  // window where neither the legacy API nor a shim was present — we prefer
  // `api.keymap.registerLayer` and fall back to `api.command.register` only
  // when the keymap surface is missing (older hosts that predate keymap).
  // See https://github.com/cortexkit/aft/issues/33.
  registerStatusCommand(api);

  // Receive server → TUI dialog requests and status invalidations over one
  // persistent WebSocket. This avoids repeated loopback HTTP connection setup,
  // which caused idle CPU usage.
  const handleNotification = async message => {
    const requestedSessionId = getSessionId(api);
    if (!requestedSessionId) return false;
    if (message.sessionId && message.sessionId !== requestedSessionId) return false;
    if (message.type !== "action") return false;
    if (message.payload?.action !== "show-status-dialog") return false;
    await showStatusDialog(api);
    return true;
  };
  startAftTuiSocket({
    getDirectory: () => api.state.path.directory ?? "",
    getSessionId: () => getSessionId(api),
    onNotification: handleNotification
  });
  const socketScopeUnsubs = [api.event?.on?.("message.updated", () => refreshAftTuiSocketScope()), api.event?.on?.("session.updated", () => refreshAftTuiSocketScope())].filter(Boolean);
  api.lifecycle?.onDispose?.(() => {
    stopAftTuiSocket();
    for (const unsub of socketScopeUnsubs) {
      try {
        unsub();
      } catch {
        // Ignore unsubscribe errors during cleanup; socket shutdown is handled separately.
      }
    }
    for (const client of rpcClients.values()) client.reset();
    rpcClients.clear();
  });

  // Show startup notifications — RPC server is already running by the time TUI loads
  void showStartupNotifications(api);
};
const id = "aft-opencode";
export default {
  id,
  tui
};