"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,
  Database,
} from "lucide-react";
import { THEME } from "@/components/project/dashboard-ui";

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={13} className="animate-spin" /> Inspecting database…
      </div>
    );
  }
  if (error) {
    return (
      <div style={errorBox}>
        <AlertCircle size={13} /> {error}
      </div>
    );
  }
  if (!data) return null;

  if (data.unsupported) {
    return (
      <div style={infoBox}>
        <Info size={13} />
        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={13} />
        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 ? THEME.subtleBg : "transparent",
                borderColor: "transparent",
                color: active ? THEME.ink : THEME.mid,
                fontWeight: active ? 500 : 400,
              }}
              aria-pressed={active}
            >
              <Table
                size={16}
                style={{
                  color: active ? THEME.ink : THEME.muted,
                  flexShrink: 0,
                }}
              />
              <span style={tableName}>{t.name}</span>
              {typeof t.approxRows === "number" && t.approxRows >= 0 && (
                <span style={rowCount}>{formatCount(t.approxRows)}</span>
              )}
            </button>
          </li>
        );
      })}
    </ul>
  );

  if (isPublic) return items;

  return (
    <div>
      {/*
        We remove the schema chevron wrapper entirely, which flattens the tree.
        Tables are now a single list, just like files inside a flat directory.
      */}
      {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";
}

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

// Clean up unused styles

const treeWrap: React.CSSProperties = {
  display: "flex",
  flexDirection: "column",
  gap: 0,
};
const list: React.CSSProperties = {
  listStyle: "none",
  margin: 0,
  padding: 0,
  display: "flex",
  flexDirection: "column",
  gap: 0,
};
const row: React.CSSProperties = {
  display: "flex",
  alignItems: "center",
  gap: 8,
  width: "100%",
  padding: "4px 8px 4px 6px",
  border: "1px solid transparent",
  borderRadius: THEME.radiusSm,
  cursor: "pointer",
  font: "inherit",
  color: "inherit",
  textAlign: "left",
  transition: "all 0.1s ease",
};
const tableName: React.CSSProperties = {
  fontSize: "0.875rem",
  color: THEME.ink,
  flex: 1,
  whiteSpace: "nowrap",
  overflow: "hidden",
  textOverflow: "ellipsis",
};
const rowCount: React.CSSProperties = {
  fontSize: "0.65rem",
  fontWeight: 600,
  color: THEME.mid,
  background: THEME.borderSoft,
  padding: "2px 6px",
  borderRadius: 999,
  flexShrink: 0,
  marginLeft: "auto",
};
const schemaHeader: React.CSSProperties = {
  display: "flex",
  alignItems: "center",
  gap: 4,
  padding: "4px 6px",
  fontSize: "0.7rem",
  fontWeight: 600,
  letterSpacing: "0.06em",
  textTransform: "uppercase",
  color: THEME.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: THEME.mid,
};
const infoBox: React.CSSProperties = {
  display: "flex",
  alignItems: "flex-start",
  gap: 6,
  padding: "8px 10px",
  fontSize: "0.74rem",
  color: THEME.mid,
  background: THEME.subtleBg,
  border: `1px dashed ${THEME.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,
};
