"use client";

/**
 * Inline tree view of tables inside a Coolify-managed database.
 * Mirrors the pattern of GiteaFileTree: lazy-fetched on first mount,
 * one-level deep, click a table to "preview" it on the right.
 *
 * Tables are grouped by schema. The default `public` schema is rendered
 * flat (no schema header) since 95% of small projects only have one
 * schema and the extra heading is just noise.
 */

import { useEffect, useState } from "react";
import { Table, ChevronDown, ChevronRight, Loader2, AlertCircle, Info } from "lucide-react";

interface IntrospectedTable {
  schema: string;
  name: string;
  approxRows?: number;
}

interface ApiResp {
  engine: string;
  tables: IntrospectedTable[];
  unsupported?: boolean;
  message?: string;
}

interface Props {
  projectId: string;
  dbUuid: string;
  selectedTable?: { schema: string; name: string };
  onSelectTable: (t: { schema: string; name: string }) => void;
}

export function DatabaseTableTree({
  projectId, dbUuid, selectedTable, onSelectTable,
}: Props) {
  const [data, setData] = useState<ApiResp | null>(null);
  const [error, setError] = useState<string | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    let cancelled = false;
    const ctrl = new AbortController();
    const t = setTimeout(() => ctrl.abort(), 12_000);
    setLoading(true);
    setError(null);

    fetch(`/api/projects/${projectId}/databases/${dbUuid}/tables`, {
      credentials: "include", signal: ctrl.signal,
    })
      .then(async r => {
        let body: unknown = {};
        try { body = await r.json(); } catch {/* keep {} */}
        if (!r.ok) {
          throw new Error((body as { error?: string }).error || `HTTP ${r.status}`);
        }
        return body as ApiResp;
      })
      .then(d => { if (!cancelled) setData(d); })
      .catch(err => {
        if (cancelled) return;
        if (err?.name === "AbortError") setError("Timed out after 12s.");
        else setError(err?.message || "Failed to list tables");
      })
      .finally(() => { clearTimeout(t); if (!cancelled) setLoading(false); });

    return () => { cancelled = true; ctrl.abort(); clearTimeout(t); };
  }, [projectId, dbUuid]);

  if (loading) {
    return (
      <div style={inline}>
        <Loader2 size={12} className="animate-spin" /> Inspecting database…
      </div>
    );
  }
  if (error) {
    return (
      <div style={errorBox}>
        <AlertCircle size={12} /> {error}
      </div>
    );
  }
  if (!data) return null;

  if (data.unsupported) {
    return (
      <div style={infoBox}>
        <Info size={12} />
        Table inspection isn't wired up for {data.engine} yet — Postgres
        is the only engine supported today.
      </div>
    );
  }

  if (data.tables.length === 0) {
    return (
      <div style={infoBox}>
        <Info size={12} />
        No user-tables found. (Pre-deploy databases often start empty.)
      </div>
    );
  }

  // Group by schema; flatten `public` since most projects only use it.
  const bySchema = new Map<string, IntrospectedTable[]>();
  for (const t of data.tables) {
    if (!bySchema.has(t.schema)) bySchema.set(t.schema, []);
    bySchema.get(t.schema)!.push(t);
  }
  const schemas = [...bySchema.keys()].sort();

  return (
    <div style={treeWrap}>
      {schemas.map(schema => (
        <SchemaGroup
          key={schema}
          schema={schema}
          tables={bySchema.get(schema)!}
          selectedTable={selectedTable}
          onSelectTable={onSelectTable}
        />
      ))}
    </div>
  );
}

function SchemaGroup({
  schema, tables, selectedTable, onSelectTable,
}: {
  schema: string;
  tables: IntrospectedTable[];
  selectedTable?: { schema: string; name: string };
  onSelectTable: (t: { schema: string; name: string }) => void;
}) {
  const isPublic = schema === "public";
  const [open, setOpen] = useState(true);

  const items = (
    <ul style={list}>
      {tables.map(t => {
        const active = selectedTable?.schema === t.schema && selectedTable?.name === t.name;
        return (
          <li key={`${t.schema}.${t.name}`}>
            <button
              type="button"
              onClick={() => onSelectTable({ schema: t.schema, name: t.name })}
              style={{
                ...row,
                background: active ? "#fffdf8" : "transparent",
                borderColor: active ? INK.ink : "transparent",
              }}
              aria-pressed={active}
            >
              <Table size={11} style={{ color: INK.mid, flexShrink: 0 }} />
              <span style={tableName}>{t.name}</span>
              {t.approxRows != null && t.approxRows > 0 && (
                <span style={rowCount}>~{formatCount(t.approxRows)}</span>
              )}
            </button>
          </li>
        );
      })}
    </ul>
  );

  if (isPublic) return items;

  return (
    <div>
      <button type="button" onClick={() => setOpen(o => !o)} style={schemaHeader}>
        {open ? <ChevronDown size={11} /> : <ChevronRight size={11} />}
        {schema}
      </button>
      {open && items}
    </div>
  );
}

function formatCount(n: number) {
  if (n < 1_000) return String(n);
  if (n < 1_000_000) return (n / 1_000).toFixed(n < 10_000 ? 1 : 0) + "k";
  return (n / 1_000_000).toFixed(1) + "M";
}

// ──────────────────────────────────────────────────

const INK = {
  ink: "#1a1a1a",
  mid: "#5f5e5a",
  muted: "#a09a90",
  borderSoft: "#efebe1",
} as const;

const treeWrap: React.CSSProperties = { display: "flex", flexDirection: "column", gap: 6 };
const list: React.CSSProperties = { listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: 1 };
const row: React.CSSProperties = {
  display: "flex", alignItems: "center", gap: 6,
  width: "100%", padding: "5px 8px",
  border: "1px solid transparent", borderRadius: 5,
  cursor: "pointer", font: "inherit", color: "inherit",
  textAlign: "left",
};
const tableName: React.CSSProperties = {
  fontSize: "0.78rem", color: INK.ink, flex: 1,
  whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis",
  fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',
};
const rowCount: React.CSSProperties = {
  fontSize: "0.68rem", color: INK.muted, flexShrink: 0,
  fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',
};
const schemaHeader: React.CSSProperties = {
  display: "flex", alignItems: "center", gap: 4,
  padding: "4px 6px",
  fontSize: "0.7rem", fontWeight: 600,
  letterSpacing: "0.06em", textTransform: "uppercase",
  color: INK.muted,
  background: "transparent", border: "none",
  cursor: "pointer", font: "inherit",
};
const inline: React.CSSProperties = {
  display: "flex", alignItems: "center", gap: 6,
  padding: "8px 10px", fontSize: "0.76rem", color: INK.mid,
};
const infoBox: React.CSSProperties = {
  display: "flex", alignItems: "flex-start", gap: 6,
  padding: "8px 10px", fontSize: "0.74rem", color: INK.mid,
  background: "#fafaf6", border: `1px dashed ${INK.borderSoft}`, borderRadius: 6,
  lineHeight: 1.4,
};
const errorBox: React.CSSProperties = {
  display: "flex", alignItems: "center", gap: 6,
  padding: "8px 10px", fontSize: "0.74rem", color: "#7a1f15",
  background: "#fbe9e7", border: `1px solid #f4c2bc`, borderRadius: 6,
};
