import { Link, useSearchParams } from 'react-router';
import { usePlugins, type PluginDependencies, type PluginItem } from './usePlugins';

// The right panel: one plugin, and the four verbs.
//
// Reads `?selected=` itself rather than being handed a plugin by the list — neither panel tells the other
// anything, so they cannot disagree.

const Row = ({ label, children }: { label: string; children: React.ReactNode }) => (
  <div className="flex gap-3 py-1.5 text-sm">
    <span className="w-28 shrink-0 text-duck-dark/50">{label}</span>
    <span className="min-w-0 text-duck-dark">{children}</span>
  </div>
);

const Button = ({
  onClick,
  disabled,
  tone = 'ghost',
  children,
}: {
  onClick: () => void;
  disabled?: boolean;
  tone?: 'primary' | 'ghost' | 'danger';
  children: React.ReactNode;
}) => {
  const tones = {
    primary: 'bg-duck-teal text-duck-yellow hover:opacity-90',
    ghost: 'border border-duck-dark/20 text-duck-dark hover:bg-duck-dark/5',
    danger: 'border border-red-300 text-red-600 hover:bg-red-50',
  };
  return (
    <button
      onClick={onClick}
      disabled={disabled}
      className={`rounded-md px-3 py-1.5 text-sm transition-colors disabled:opacity-40 ${tones[tone]}`}
    >
      {children}
    </button>
  );
};

/** What the tree declared. Shown because "installed but nothing happened" is otherwise a mystery. */
const Parts = ({ has }: { has: PluginItem['has'] }) => {
  const parts = [
    ['api', has.api],
    ['schema', has.schema],
    ['sidecar', has.sidecar],
    ['web', has.web],
  ] as const;
  const present = parts.filter(([, yes]) => yes).map(([name]) => name);
  return <>{present.length ? present.join(' · ') : 'manifest only'}</>;
};

/**
 * Host binaries, per binary, before anything is installed.
 *
 * The whole point is that this is visible BEFORE the button. Music needs `ffprobe`, and without it the
 * indexer does not fail — it writes a library where every track is a bare filename and reports success.
 * So "what does this need, and does this machine have it" is a question the owner answers first, not
 * something they find out from a log.
 *
 * Rendered only when the plugin declares something. Most declare nothing, and a permanently empty
 * "Requires: nothing" row is noise on every other plugin's page.
 */
const Dependencies = ({ deps }: { deps: PluginDependencies }) => {
  if (!deps.items.length) return null;

  return (
    <div className="mt-4 border-t border-duck-dark/10 pt-4">
      <p className="text-xs font-medium uppercase tracking-wide text-duck-dark/40">Needs on this machine</p>
      <ul className="mt-2 space-y-2">
        {deps.items.map((item) => (
          <li key={item.binary} className="flex gap-2 text-sm">
            <span aria-hidden className={item.present ? 'text-emerald-600' : 'text-amber-600'}>
              {item.present ? '✓' : '•'}
            </span>
            <span className="min-w-0">
              <code className="text-duck-dark">{item.binary}</code>{' '}
              <span className={item.present ? 'text-duck-dark/50' : 'text-amber-700'}>
                {item.present ? 'present' : `missing${item.packageName ? ` — ${item.packageName}` : ''}`}
              </span>
              <span className="block text-xs text-duck-dark/50">{item.reason}</span>
            </span>
          </li>
        ))}
      </ul>

      {/* Three states, and they need different words. Satisfied says nothing further. Installable says
          exactly what will be run as root, because that is what is being consented to. Blocked gives the
          command to run by hand, which is the whole escape hatch on a machine without passwordless sudo. */}
      {deps.satisfied ? null : deps.canInstall ? (
        <p className="mt-3 text-xs text-duck-dark/60">
          Installing this plugin will run{' '}
          <code className="text-duck-dark">
            {deps.manager} install {deps.packagesToInstall.join(' ')}
          </code>{' '}
          as root first.
        </p>
      ) : (
        <div className="mt-3 rounded border border-amber-500/30 bg-amber-500/5 p-2.5">
          <p className="text-xs text-amber-800">{deps.blockedReason ?? 'These cannot be installed automatically.'}</p>
          {deps.manualHint ? <code className="mt-1.5 block text-xs text-duck-dark">{deps.manualHint}</code> : null}
        </div>
      )}
    </div>
  );
};

export const PluginDetail = () => {
  const { plugins, steps, result, running, run } = usePlugins();
  const [params] = useSearchParams();
  const plugin = plugins.find((p) => p.appName === params.get('selected'));

  if (!plugin) {
    return <div className="p-6 text-sm text-duck-dark/50">Select a plugin.</div>;
  }

  const busy = running !== null;

  return (
    <div className="h-full overflow-auto p-6">
      <h2 className="text-lg font-semibold text-duck-dark">{plugin.label}</h2>
      <p className="mt-1 text-sm text-duck-dark/60">{plugin.summary}</p>

      <div className="mt-5 border-t border-duck-dark/10 pt-4">
        <Row label="Mounts at">
          <code>/api{plugin.prefix}</code>
        </Row>
        <Row label="Publisher">{plugin.publisher}</Row>
        <Row label="Version">
          {plugin.version}
          {plugin.outdated ? (
            <span className="ml-2 text-amber-600">on disk — installed {plugin.installedVersion}</span>
          ) : null}
        </Row>
        <Row label="Needs platform">{plugin.platform}</Row>
        <Row label="Ships">
          <Parts has={plugin.has} />
        </Row>
        {plugin.has.sidecar ? (
          <Row label="Sidecar">
            {/* Enabled but not online is the state worth naming: the plugin is switched on and its
                process is not running, which is broken rather than off. */}
            <span
              className={
                plugin.enabled && plugin.processStatus !== 'online' && plugin.installed
                  ? 'text-amber-600'
                  : 'text-duck-dark'
              }
            >
              {plugin.processStatus ?? 'not started'}
            </span>
          </Row>
        ) : null}
        <Row label="Permissions">
          {plugin.permissions.length
            ? plugin.permissions.map((p) => `${p.key}${p.ownerOnly ? ' (owner only)' : ''}`).join(', ')
            : 'none — reachable by anyone who can reach the platform'}
        </Row>
      </div>

      <Dependencies deps={plugin.dependencies} />

      {/* The way in, when there is one.
          Only while installed AND enabled: a link to a route that is not mounted is a link to the home
          page, since the shell redirects an unknown path — which reads as the link being broken rather
          than the plugin being off. `has.web` because a plugin can be entirely backend and have no screen
          to open at all. */}
      {plugin.installed && plugin.enabled && plugin.has.web ? (
        <Link
          to={plugin.prefix}
          className="mt-6 inline-flex items-center gap-1.5 text-sm text-duck-teal hover:underline"
        >
          Open {plugin.label}
          <span aria-hidden>→</span>
        </Link>
      ) : null}

      <div className="mt-6 flex flex-wrap gap-2">
        {!plugin.installed ? (
          // Disabled when the host binaries cannot be satisfied, because the installer refuses in that
          // case — offering a button whose only outcome is an error message is worse than not offering
          // it. The reason is already on screen above, so the disabled state is not mysterious.
          <Button
            tone="primary"
            disabled={busy || (!plugin.dependencies.satisfied && !plugin.dependencies.canInstall)}
            onClick={() => run('install', plugin.appName)}
          >
            Install
          </Button>
        ) : (
          <>
            {plugin.enabled ? (
              <Button disabled={busy} onClick={() => run('disable', plugin.appName)}>
                Disable
              </Button>
            ) : (
              <Button tone="primary" disabled={busy} onClick={() => run('enable', plugin.appName)}>
                Enable
              </Button>
            )}
            {plugin.outdated ? (
              <Button disabled={busy} onClick={() => run('install', plugin.appName)}>
                Update to {plugin.version}
              </Button>
            ) : null}
            <Button tone="danger" disabled={busy} onClick={() => run('uninstall', plugin.appName)}>
              Uninstall
            </Button>
          </>
        )}
      </div>

      {/* The log, as it arrives. `steps` fills while the stream is open and `result` lands when it
          closes, so the same block is both the progress and the verdict — an install that mounted routes
          but could not start a sidecar reads differently from one that worked, and a spinner cannot. */}
      {steps.length || result ? (
        <div className="mt-5 rounded-md border border-duck-dark/10 bg-duck-dark/[0.02] p-3">
          <div className="mb-1.5 flex items-center gap-2 text-xs font-medium text-duck-dark/60">
            {running ? (
              <>
                <span className="inline-block h-1.5 w-1.5 animate-pulse rounded-full bg-duck-teal" />
                {running}…
              </>
            ) : (
              <span className={result?.ok ? 'text-emerald-600' : 'text-red-600'}>{result?.ok ? 'Done' : 'Failed'}</span>
            )}
          </div>
          <ul className="space-y-0.5 font-mono text-xs text-duck-dark/70">
            {steps.map((s, i) => (
              <li key={i}>· {s}</li>
            ))}
          </ul>
          {result?.error ? <div className="mt-2 text-xs text-red-600">{result.error}</div> : null}
        </div>
      ) : null}

      {/* Uninstall keeps every table and row the plugin owns, so this is worth saying rather than
          leaving someone to guess whether the button destroys their data. */}
      {plugin.installed ? (
        <p className="mt-4 text-xs text-duck-dark/40">
          Disabling unmounts its routes and stops its sidecar. Uninstalling also forgets the install — neither deletes
          anything the plugin stored.
        </p>
      ) : null}
    </div>
  );
};
