import type { GiteaContentsEntry, GiteaRepo } from './shared';
import { useMemo, useState } from 'react';
import { Link } from 'react-router';
import { ChevronDown, CornerLeftUp, File, FileCode, Folder, GitBranch, Link2, Tag } from 'lucide-react';
import { CodeRenderer } from '../FileViewer/renderers';
import { getLang } from '../FileViewer/file-types';
import { EmptyState, ErrorState, Loading } from './GiteaBits';
import { GiteaMarkdown } from './GiteaMarkdown';
import { decodeBase64, formatBytes, giteaRepoPath, timeAgo } from './shared';
import { useGiteaBranches, useGiteaContents, useGiteaTags } from './useGiteaData';

// The Code tab: a directory listing or a single file, whichever the ?path= points at, plus the rendered
// README underneath a directory — which is the layout Gitea's own repository page uses.
//
// There is no recursive tree sidebar on purpose. Gitea's web UI browses one directory at a time off the
// contents API, and doing the same means one request per navigation instead of walking the whole tree of a
// repository that might have thousands of files in it.

type RepoCodeViewProps = { repo: GiteaRepo; owner: string; name: string; path: string; refName: string };

const README = /^readme(\.(md|markdown|rst|txt))?$/i;

export const RepoCodeView = ({ repo, owner, name, path, refName }: RepoCodeViewProps) => {
  const { data, isLoading, error } = useGiteaContents({ owner, repo: name }, { path, ref: refName });

  if (repo.empty) {
    return <EmptyState title="This repository is empty" hint="Nothing has been pushed to it yet." />;
  }
  if (isLoading) return <Loading label="Loading files…" />;
  if (error) return <ErrorState title="Could not read the repository" error={error} />;
  if (!data) return <EmptyState title="Nothing here" />;

  return (
    <div className="flex h-full flex-col">
      <div className="flex flex-wrap items-center gap-2 border-b px-4 py-2">
        <RefPicker owner={owner} name={name} repo={repo} path={path} refName={refName} />
        <Breadcrumbs owner={owner} name={name} path={path} refName={refName} />
      </div>
      <div className="min-h-0 flex-1 overflow-y-auto">
        {Array.isArray(data) ? (
          <DirectoryView entries={data} owner={owner} name={name} path={path} refName={refName} repo={repo} />
        ) : (
          <FileView entry={data} repo={repo} refName={refName} />
        )}
      </div>
    </div>
  );
};

// ── Ref switcher ──────────────────────────────────────────────────────────────────────────────────

type RefPickerProps = { owner: string; name: string; repo: GiteaRepo; path: string; refName: string };

/**
 * Branch and tag switcher. Both lists are fetched lazily — only once the menu is opened — because most
 * visits to a repository never touch it, and a repo with a few hundred tags would otherwise pay for them
 * on every page load.
 */
const RefPicker = ({ owner, name, repo, path, refName }: RefPickerProps) => {
  const [open, setOpen] = useState(false);
  const { data: branches } = useGiteaBranches({ owner, repo: name }, { enabled: open });
  const { data: tags } = useGiteaTags({ owner, repo: name }, { enabled: open });

  return (
    <div className="relative">
      <button
        type="button"
        onClick={() => setOpen((v) => !v)}
        onBlur={() => setTimeout(() => setOpen(false), 150)}
        className="flex items-center gap-1.5 rounded-md border px-2 py-1 text-xs font-medium transition-colors hover:bg-muted"
      >
        <GitBranch className="h-3 w-3" />
        <span className="max-w-[12rem] truncate">{refName}</span>
        <ChevronDown className="h-3 w-3 text-muted-foreground" />
      </button>
      {open && (
        <div className="absolute left-0 top-full z-20 mt-1 max-h-80 w-64 overflow-y-auto rounded-lg border bg-popover p-1 shadow-lg">
          <div className="px-2 py-1 text-xs font-semibold uppercase tracking-wide text-muted-foreground">Branches</div>
          {(branches ?? []).map((branch) => (
            <Link
              key={branch.name}
              to={giteaRepoPath(owner, name, { tab: 'code', path, ref: branch.name })}
              className={`flex items-center gap-2 rounded-md px-2 py-1.5 text-xs hover:bg-muted ${
                branch.name === refName ? 'font-medium text-primary' : ''
              }`}
            >
              <GitBranch className="h-3 w-3 shrink-0 text-muted-foreground" />
              <span className="truncate">{branch.name}</span>
              {branch.name === repo.default_branch && (
                <span className="ml-auto shrink-0 text-xs text-muted-foreground">default</span>
              )}
            </Link>
          ))}
          {!!tags?.length && (
            <>
              <div className="mt-1 border-t px-2 pb-1 pt-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground">
                Tags
              </div>
              {tags.map((tag) => (
                <Link
                  key={tag.name}
                  to={giteaRepoPath(owner, name, { tab: 'code', path, ref: tag.name })}
                  className={`flex items-center gap-2 rounded-md px-2 py-1.5 text-xs hover:bg-muted ${
                    tag.name === refName ? 'font-medium text-primary' : ''
                  }`}
                >
                  <Tag className="h-3 w-3 shrink-0 text-muted-foreground" />
                  <span className="truncate">{tag.name}</span>
                </Link>
              ))}
            </>
          )}
        </div>
      )}
    </div>
  );
};

// ── Breadcrumbs ───────────────────────────────────────────────────────────────────────────────────

type BreadcrumbProps = { owner: string; name: string; path: string; refName: string };

const Breadcrumbs = ({ owner, name, path, refName }: BreadcrumbProps) => {
  const segments = path ? path.split('/') : [];

  return (
    <div className="flex min-w-0 flex-wrap items-center gap-1 text-sm">
      <Link
        to={giteaRepoPath(owner, name, { tab: 'code', ref: refName })}
        className="font-medium text-primary hover:underline"
      >
        {name}
      </Link>
      {segments.map((segment, i) => {
        const upto = segments.slice(0, i + 1).join('/');
        const last = i === segments.length - 1;
        return (
          <span key={upto} className="flex min-w-0 items-center gap-1">
            <span className="text-muted-foreground">/</span>
            {last ? (
              <span className="truncate font-medium">{segment}</span>
            ) : (
              <Link
                to={giteaRepoPath(owner, name, { tab: 'code', path: upto, ref: refName })}
                className="truncate text-primary hover:underline"
              >
                {segment}
              </Link>
            )}
          </span>
        );
      })}
    </div>
  );
};

// ── Directory ─────────────────────────────────────────────────────────────────────────────────────

type DirectoryProps = {
  entries: GiteaContentsEntry[];
  owner: string;
  name: string;
  path: string;
  refName: string;
  repo: GiteaRepo;
};

const DirectoryView = ({ entries, owner, name, path, refName, repo }: DirectoryProps) => {
  // Directories first, then files, each alphabetical — the ordering every file browser uses and the one
  // Gitea's own listing applies. The API returns them in git's order, which is neither.
  const sorted = useMemo(
    () =>
      [...entries].sort((a, b) => {
        const aDir = a.type === 'dir';
        const bDir = b.type === 'dir';
        if (aDir !== bDir) return aDir ? -1 : 1;
        return a.name.localeCompare(b.name);
      }),
    [entries],
  );

  const readme = sorted.find((entry) => entry.type === 'file' && README.test(entry.name));
  const parent = path.includes('/') ? path.slice(0, path.lastIndexOf('/')) : '';

  return (
    <div className="flex flex-col">
      <div className="divide-y">
        {path && (
          <Link
            to={giteaRepoPath(owner, name, { tab: 'code', path: parent, ref: refName })}
            className="flex items-center gap-2 px-4 py-2 text-sm text-muted-foreground transition-colors hover:bg-muted/50"
          >
            <CornerLeftUp className="h-4 w-4" />
            <span>..</span>
          </Link>
        )}
        {sorted.map((entry) => (
          <EntryRow key={entry.path} entry={entry} owner={owner} name={name} refName={refName} />
        ))}
      </div>

      {readme && <ReadmePanel entry={readme} repo={repo} owner={owner} name={name} refName={refName} />}
    </div>
  );
};

type EntryRowProps = { entry: GiteaContentsEntry; owner: string; name: string; refName: string };

const EntryRow = ({ entry, owner, name, refName }: EntryRowProps) => {
  const isDir = entry.type === 'dir';
  const Icon = isDir ? Folder : entry.type === 'symlink' ? Link2 : File;

  return (
    <Link
      to={giteaRepoPath(owner, name, { tab: 'code', path: entry.path, ref: refName })}
      className="flex items-center gap-3 px-4 py-2 text-sm transition-colors hover:bg-muted/50"
    >
      <Icon className={`h-4 w-4 shrink-0 ${isDir ? 'text-info' : 'text-muted-foreground'}`} />
      <span className="min-w-0 flex-1 truncate">{entry.name}</span>
      {entry.last_commit_message && (
        <span className="hidden min-w-0 flex-[2] truncate text-xs text-muted-foreground md:block">
          {entry.last_commit_message.split('\n')[0]}
        </span>
      )}
      <span className="shrink-0 text-xs tabular-nums text-muted-foreground">
        {entry.last_committer_date ? timeAgo(entry.last_committer_date) : !isDir ? formatBytes(entry.size) : ''}
      </span>
    </Link>
  );
};

// ── README ────────────────────────────────────────────────────────────────────────────────────────

type ReadmeProps = { entry: GiteaContentsEntry; repo: GiteaRepo; owner: string; name: string; refName: string };

/**
 * The listing's own README entry carries no content — the contents API only fills `content` in when the
 * path names a single file — so it is fetched again by path. That second request is what Gitea's own page
 * does too.
 */
const ReadmePanel = ({ entry, repo, owner, name, refName }: ReadmeProps) => {
  const { data } = useGiteaContents({ owner, repo: name }, { path: entry.path, ref: refName });
  const file = Array.isArray(data) ? null : data;
  if (!file?.content) return null;

  const isMarkdown = /\.(md|markdown)$/i.test(entry.name);
  const text = decodeBase64(file.content);

  return (
    <div className="m-4 overflow-hidden rounded-lg border">
      <div className="flex items-center gap-2 border-b bg-muted/40 px-4 py-2 text-xs font-medium">
        <FileCode className="h-3.5 w-3.5 text-muted-foreground" />
        {entry.name}
      </div>
      <div className="px-4 py-3">
        {isMarkdown ? (
          <GiteaMarkdown
            content={text}
            repoUrl={repo.html_url}
            refName={refName}
            basePath={entry.path.includes('/') ? entry.path.slice(0, entry.path.lastIndexOf('/')) : ''}
          />
        ) : (
          <pre className="whitespace-pre-wrap font-mono text-xs leading-relaxed">{text}</pre>
        )}
      </div>
    </div>
  );
};

// ── Single file ───────────────────────────────────────────────────────────────────────────────────

type FileViewProps = { entry: GiteaContentsEntry; repo: GiteaRepo; refName: string };

const FileView = ({ entry, repo, refName }: FileViewProps) => {
  if (entry.type === 'submodule') {
    return <EmptyState title={`${entry.name} is a submodule`} hint={entry.submodule_git_url} />;
  }
  if (entry.type === 'symlink') {
    return <EmptyState title={`${entry.name} is a symlink`} hint={`→ ${entry.target ?? 'unknown target'}`} />;
  }
  // Gitea omits `content` above a size limit rather than streaming a megabyte of base64 into a JSON body.
  if (!entry.content) {
    return (
      <EmptyState
        title="Too large to display"
        hint={
          entry.download_url ? (
            <a href={entry.download_url} target="_blank" rel="noreferrer" className="text-primary hover:underline">
              Download {entry.name} ({formatBytes(entry.size)})
            </a>
          ) : (
            `${entry.name} is ${formatBytes(entry.size)}`
          )
        }
      />
    );
  }

  const text = decodeBase64(entry.content);
  const isMarkdown = /\.(md|markdown)$/i.test(entry.name);

  return (
    <div>
      <div className="flex items-center justify-between gap-2 border-b bg-muted/40 px-4 py-2 text-xs">
        <span className="truncate font-medium">{entry.name}</span>
        <span className="flex shrink-0 items-center gap-3 text-muted-foreground">
          <span className="tabular-nums">{formatBytes(entry.size)}</span>
          {entry.download_url && (
            <a href={entry.download_url} target="_blank" rel="noreferrer" className="hover:text-foreground">
              Raw
            </a>
          )}
        </span>
      </div>
      {isMarkdown ? (
        <div className="px-4 py-3">
          <GiteaMarkdown
            content={text}
            repoUrl={repo.html_url}
            refName={refName}
            basePath={entry.path.includes('/') ? entry.path.slice(0, entry.path.lastIndexOf('/')) : ''}
          />
        </div>
      ) : (
        <CodeRenderer content={text} lang={getLang(entry.name)} />
      )}
    </div>
  );
};
