import React, { FormEvent, useEffect, useMemo, useState } from 'react';
import { createRoot } from 'react-dom/client';
import {
  ApiError,
  Economy,
  Governance,
  ListingDetail,
  ListingSummary,
  PanelName,
  Wallet,
  api,
} from './api';
import './styles.css';

const OPERATOR_PRESETS = ['m2bd', 'sdjs-operator', 'chris-operator', 'gunnar-operator'];
const OPERATOR_STORAGE_KEY = 'm2-market.operator';

type Route =
  | { name: 'login' }
  | { name: 'search' }
  | { name: 'listing'; id: string }
  | { name: 'wallet' }
  | { name: 'economy' }
  | { name: 'governance' };

function routeFromHash(): Route {
  const hash = window.location.hash.replace(/^#/, '') || '/';
  const [path] = hash.split('?');
  const parts = path.split('/').filter(Boolean);

  if (parts[0] === 'login') return { name: 'login' };
  if (parts[0] === 'listing' && parts[1]) return { name: 'listing', id: decodeURIComponent(parts[1]) };
  if (parts[0] === 'wallet') return { name: 'wallet' };
  if (parts[0] === 'economy') return { name: 'economy' };
  if (parts[0] === 'governance') return { name: 'governance' };
  return { name: 'search' };
}

function useRoute(): Route {
  const [route, setRoute] = useState(routeFromHash);

  useEffect(() => {
    const onChange = () => setRoute(routeFromHash());
    window.addEventListener('hashchange', onChange);
    return () => window.removeEventListener('hashchange', onChange);
  }, []);

  return route;
}

function formatM2cr(value: number): string {
  return `${new Intl.NumberFormat('en-US', { maximumFractionDigits: 2 }).format(value)} m2cr`;
}

function formatDate(value?: string): string {
  if (!value) return 'unknown';
  const date = new Date(value);
  if (Number.isNaN(date.getTime())) return value;
  return date.toLocaleString();
}

function getPanelError(error: unknown, panel: PanelName): string | null {
  const panelAliases: Record<PanelName, string[]> = {
    search: ['search', 'catalog'],
    listing: ['listing', 'catalog'],
    wallet: ['wallet', 'ledger'],
    economy: ['economy', 'ledger'],
    governance: ['governance', 'registry', 'forgejo'],
    login: ['login'],
  };

  if (
    error instanceof ApiError &&
    error.status === 502 &&
    (!error.panel || panelAliases[panel].includes(error.panel))
  ) {
    return error.message;
  }
  if (error instanceof ApiError && error.status === 502) return null;
  if (error instanceof ApiError && error.status !== 401) return error.message;
  if (error instanceof Error) return error.message;
  return null;
}

function PanelError({ panel, error }: { panel: PanelName; error: unknown }) {
  const message = getPanelError(error, panel);
  if (!message) return null;
  return (
    <div className="panel-error" role="status">
      <strong>{panel} rail unavailable</strong>
      <span>{message}</span>
    </div>
  );
}

function App() {
  const route = useRoute();
  const [operator, setOperator] = useState(() => localStorage.getItem(OPERATOR_STORAGE_KEY) || OPERATOR_PRESETS[0]);

  useEffect(() => {
    localStorage.setItem(OPERATOR_STORAGE_KEY, operator);
  }, [operator]);

  if (route.name === 'login') {
    return <LoginPage />;
  }

  return (
    <div className="app-shell">
      <header className="topbar">
        <a className="brand" href="#/">
          <span className="brand-mark">m2</span>
          <span>
            <strong>market</strong>
            <small>machine.machine</small>
          </span>
        </a>
        <nav className="nav" aria-label="Primary">
          <NavLink href="#/" active={route.name === 'search'} label="Search" />
          <NavLink href="#/wallet" active={route.name === 'wallet'} label="Wallet" />
          <NavLink href="#/economy" active={route.name === 'economy'} label="Economy" />
          <NavLink href="#/governance" active={route.name === 'governance'} label="Governance" />
        </nav>
        <OperatorPicker value={operator} onChange={setOperator} />
      </header>

      <main className="main-grid">
        {route.name === 'search' && <SearchPage />}
        {route.name === 'listing' && <ListingPage listingId={route.id} />}
        {route.name === 'wallet' && <WalletPage operator={operator} />}
        {route.name === 'economy' && <EconomyPage />}
        {route.name === 'governance' && <GovernancePage />}
      </main>
    </div>
  );
}

function NavLink({ href, active, label }: { href: string; active: boolean; label: string }) {
  return (
    <a className={active ? 'active' : ''} href={href}>
      {label}
    </a>
  );
}

function LoginPage() {
  const [passcode, setPasscode] = useState('');
  const [error, setError] = useState<string | null>(null);
  const [busy, setBusy] = useState(false);

  async function submit(event: FormEvent) {
    event.preventDefault();
    setBusy(true);
    setError(null);

    try {
      await api.login(passcode);
      window.location.hash = '#/';
    } catch (err) {
      setError(err instanceof ApiError && err.status === 401 ? 'Passcode rejected.' : 'Login failed.');
    } finally {
      setBusy(false);
    }
  }

  return (
    <main className="login-page">
      <section className="login-panel">
        <div className="brand login-brand">
          <span className="brand-mark">m2</span>
          <span>
            <strong>market</strong>
            <small>machine.machine</small>
          </span>
        </div>
        <form onSubmit={submit} className="login-form">
          <label htmlFor="passcode">Fleet passcode</label>
          <input
            id="passcode"
            autoFocus
            type="password"
            value={passcode}
            onChange={(event) => setPasscode(event.target.value)}
            autoComplete="current-password"
          />
          {error && <p className="form-error">{error}</p>}
          <button disabled={busy || passcode.trim().length === 0}>{busy ? 'Checking...' : 'Enter'}</button>
        </form>
      </section>
    </main>
  );
}

function OperatorPicker({ value, onChange }: { value: string; onChange: (value: string) => void }) {
  const presetValue = OPERATOR_PRESETS.includes(value) ? value : '';

  return (
    <section className="operator-picker" aria-label="Operator picker">
      <select
        value={presetValue}
        onChange={(event) => {
          if (event.target.value) onChange(event.target.value);
        }}
      >
        <option value="" disabled>
          custom
        </option>
        {OPERATOR_PRESETS.map((operator) => (
          <option key={operator} value={operator}>
            {operator}
          </option>
        ))}
      </select>
      <input value={value} onChange={(event) => onChange(event.target.value)} aria-label="Operator id" />
    </section>
  );
}

function SearchPage() {
  const [query, setQuery] = useState('pdf report');
  const [submittedQuery, setSubmittedQuery] = useState('pdf report');
  const [results, setResults] = useState<ListingSummary[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<unknown>(null);

  useEffect(() => {
    let cancelled = false;
    setLoading(true);
    setError(null);
    api
      .search(submittedQuery, 10)
      .then((payload) => {
        if (!cancelled) setResults(payload);
      })
      .catch((err) => {
        if (!cancelled) setError(err);
      })
      .finally(() => {
        if (!cancelled) setLoading(false);
      });
    return () => {
      cancelled = true;
    };
  }, [submittedQuery]);

  function submit(event: FormEvent) {
    event.preventDefault();
    setSubmittedQuery(query.trim());
  }

  return (
    <section className="view">
      <div className="view-heading">
        <p>Catalog</p>
        <h1>Search listings</h1>
      </div>
      <form className="search-bar" onSubmit={submit}>
        <input value={query} onChange={(event) => setQuery(event.target.value)} placeholder="Search catalog" />
        <button>Search</button>
      </form>
      <PanelError panel="search" error={error} />
      {loading && <div className="empty-state">Loading catalog results...</div>}
      {!loading && !error && results.length === 0 && <div className="empty-state">No visible listings matched.</div>}
      <div className="cards-grid">
        {results.map((listing) => (
          <ListingCard key={listing.listing_id} listing={listing} />
        ))}
      </div>
    </section>
  );
}

function ListingCard({ listing }: { listing: ListingSummary }) {
  return (
    <article className="listing-card">
      <div>
        <p className="eyebrow">{listing.category || 'solution'}</p>
        <h2>{listing.name}</h2>
        <p>{listing.summary}</p>
      </div>
      <dl className="metrics">
        <div>
          <dt>price</dt>
          <dd>{formatM2cr(listing.price.amount)}</dd>
        </div>
        <div>
          <dt>installs</dt>
          <dd>{listing.stats?.installs ?? 0}</dd>
        </div>
      </dl>
      <a className="text-link" href={`#/listing/${encodeURIComponent(listing.listing_id)}`}>
        Open listing
      </a>
    </article>
  );
}

function ListingPage({ listingId }: { listingId: string }) {
  const [listing, setListing] = useState<ListingDetail | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<unknown>(null);
  const [copied, setCopied] = useState(false);

  useEffect(() => {
    let cancelled = false;
    setLoading(true);
    setError(null);
    api
      .listing(listingId)
      .then((payload) => {
        if (!cancelled) setListing(payload);
      })
      .catch((err) => {
        if (!cancelled) setError(err);
      })
      .finally(() => {
        if (!cancelled) setLoading(false);
      });
    return () => {
      cancelled = true;
    };
  }, [listingId]);

  async function copyCommand(command: string) {
    await navigator.clipboard.writeText(command);
    setCopied(true);
    window.setTimeout(() => setCopied(false), 1800);
  }

  return (
    <section className="view">
      <a className="text-link back-link" href="#/">
        Back to search
      </a>
      <PanelError panel="listing" error={error} />
      {loading && <div className="empty-state">Loading listing...</div>}
      {listing && (
        <>
          <div className="listing-hero">
            <p className="eyebrow">{listing.category || listing.listing_id}</p>
            <h1>{listing.name}</h1>
            <p>{listing.summary}</p>
            <dl className="metrics wide">
              <div>
                <dt>seller</dt>
                <dd>{listing.seller}</dd>
              </div>
              <div>
                <dt>version</dt>
                <dd>{listing.version || listing.solution_version || 'unknown'}</dd>
              </div>
              <div>
                <dt>price</dt>
                <dd>{formatM2cr(listing.price.amount)}</dd>
              </div>
              <div>
                <dt>installs</dt>
                <dd>{listing.stats?.installs ?? 0}</dd>
              </div>
            </dl>
          </div>

          <div className="two-column">
            <section className="surface">
              <p className="eyebrow">Evidence</p>
              <p>{listing.evidence_summary}</p>
            </section>
            <section className="surface">
              <p className="eyebrow">Permissions</p>
              {listing.permissions.length === 0 ? (
                <p>No extra permissions declared.</p>
              ) : (
                <ul className="plain-list">
                  {listing.permissions.map((permission) => (
                    <li key={permission}>{permission}</li>
                  ))}
                </ul>
              )}
            </section>
          </div>

          <section className="handoff">
            <div>
              <p className="eyebrow">Install handoff</p>
              <h2>Run this on your desktop terminal</h2>
              {listing.install_ref && <p>Install ref: {listing.install_ref}</p>}
            </div>
            <code>{listing.install_command}</code>
            <button onClick={() => copyCommand(listing.install_command)}>{copied ? 'Copied' : 'Copy command'}</button>
          </section>
        </>
      )}
    </section>
  );
}

function WalletPage({ operator }: { operator: string }) {
  const [wallet, setWallet] = useState<Wallet | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<unknown>(null);

  useEffect(() => {
    let cancelled = false;
    setLoading(true);
    setError(null);
    api
      .wallet(operator.trim() || 'unknown')
      .then((payload) => {
        if (!cancelled) setWallet(payload);
      })
      .catch((err) => {
        if (!cancelled) setError(err);
      })
      .finally(() => {
        if (!cancelled) setLoading(false);
      });
    return () => {
      cancelled = true;
    };
  }, [operator]);

  return (
    <section className="view">
      <div className="view-heading">
        <p>Wallet</p>
        <h1>{operator || 'unknown operator'}</h1>
      </div>
      <PanelError panel="wallet" error={error} />
      {loading && <div className="empty-state">Loading wallet...</div>}
      {wallet && (
        <>
          <section className="balance-strip">
            <div>
              <span>balance</span>
              <strong>{formatM2cr(wallet.balance)}</strong>
            </div>
            <p>as of {formatDate(wallet.as_of)}</p>
          </section>
          {wallet.balance === 0 && wallet.transactions.length === 0 && (
            <div className="empty-state">No such wallet yet, or the wallet has no activity.</div>
          )}
          <TransactionTable transactions={wallet.transactions} />
        </>
      )}
    </section>
  );
}

function TransactionTable({ transactions }: { transactions: Wallet['transactions'] }) {
  if (transactions.length === 0) return null;

  return (
    <div className="table-wrap">
      <table>
        <thead>
          <tr>
            <th>Time</th>
            <th>From</th>
            <th>To</th>
            <th>Amount</th>
            <th>Reason</th>
            <th>Ref</th>
          </tr>
        </thead>
        <tbody>
          {transactions.map((tx, index) => (
            <tr key={`${tx.ts}-${tx.ref ?? index}`}>
              <td>{formatDate(tx.ts)}</td>
              <td>{tx.from}</td>
              <td>{tx.to}</td>
              <td>{formatM2cr(tx.amount)}</td>
              <td>{tx.reason}</td>
              <td>{tx.ref || '-'}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

function EconomyPage() {
  const [economy, setEconomy] = useState<Economy | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<unknown>(null);

  useEffect(() => {
    let cancelled = false;
    setLoading(true);
    setError(null);
    api
      .economy()
      .then((payload) => {
        if (!cancelled) setEconomy(payload);
      })
      .catch((err) => {
        if (!cancelled) setError(err);
      })
      .finally(() => {
        if (!cancelled) setLoading(false);
      });
    return () => {
      cancelled = true;
    };
  }, []);

  const total = useMemo(() => economy?.operators.reduce((sum, operator) => sum + operator.balance, 0) ?? 0, [economy]);

  return (
    <section className="view">
      <div className="view-heading">
        <p>Economy</p>
        <h1>Fleet balances</h1>
      </div>
      <PanelError panel="economy" error={error} />
      {loading && <div className="empty-state">Loading economy...</div>}
      {economy && (
        <>
          <section className="balance-strip">
            <div>
              <span>total</span>
              <strong>{formatM2cr(total)}</strong>
            </div>
            {economy.audit ? (
              <a className="text-link" href={economy.audit.url} target="_blank" rel="noreferrer">
                Audit snapshot {economy.audit.date}
              </a>
            ) : (
              <p>No audit snapshot linked.</p>
            )}
          </section>
          <div className="cards-grid compact">
            {economy.operators.map((operator) => (
              <article className="mini-card" key={operator.operator_id}>
                <span>{operator.operator_id}</span>
                <strong>{formatM2cr(operator.balance)}</strong>
              </article>
            ))}
          </div>
        </>
      )}
    </section>
  );
}

function GovernancePage() {
  const [governance, setGovernance] = useState<Governance | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<unknown>(null);

  useEffect(() => {
    let cancelled = false;
    setLoading(true);
    setError(null);
    api
      .governance()
      .then((payload) => {
        if (!cancelled) setGovernance(payload);
      })
      .catch((err) => {
        if (!cancelled) setError(err);
      })
      .finally(() => {
        if (!cancelled) setLoading(false);
      });
    return () => {
      cancelled = true;
    };
  }, []);

  return (
    <section className="view">
      <div className="view-heading">
        <p>Governance</p>
        <h1>Registry review</h1>
      </div>
      <PanelError panel="governance" error={error} />
      {loading && <div className="empty-state">Loading governance...</div>}
      {governance && (
        <div className="two-column">
          <section className="surface">
            <p className="eyebrow">Open PRs</p>
            {governance.open_prs.length === 0 ? (
              <p>No open listing PRs.</p>
            ) : (
              <ul className="review-list">
                {governance.open_prs.map((pr) => (
                  <li key={pr.number}>
                    <a href={pr.url} target="_blank" rel="noreferrer">
                      #{pr.number} {pr.title}
                    </a>
                    <span>{pr.age_days}d open</span>
                    <div className="labels">
                      {pr.labels.map((label) => (
                        <span key={label}>{label}</span>
                      ))}
                    </div>
                  </li>
                ))}
              </ul>
            )}
          </section>
          <section className="surface">
            <p className="eyebrow">Listings</p>
            {governance.listings.length === 0 ? (
              <p>No listings returned.</p>
            ) : (
              <ul className="review-list">
                {governance.listings.map((listing) => (
                  <li key={listing.listing_id}>
                    <a href={listing.url} target="_blank" rel="noreferrer">
                      {listing.listing_id}
                    </a>
                    <span className={`status ${listing.status}`}>{listing.status}</span>
                  </li>
                ))}
              </ul>
            )}
          </section>
        </div>
      )}
    </section>
  );
}

createRoot(document.getElementById('root') as HTMLElement).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
);
