/**
 * GET /api/files/<path...>
 *   ?download=1   → force Content-Disposition: attachment
 *
 * Dataset-wide file reader. Path is dataDir-relative — `tmp/foo.md`,
 * `scratch/report.md`, `flows/x.yaml`, etc. Pairs with the chat builtin
 * `read_forge_file` and the in-browser viewer at /files/<path>.
 *
 * Sensitive top-level items (encryption keys, sqlite DBs, server log,
 * `*-tokens.json`) are refused — same blacklist as read_forge_file so
 * chat-emitted links can't accidentally expose secrets.
 *
 * /api/scratch/<path> is the older route that resolves under
 * <dataDir>/scratch/ only — kept for legacy URLs / link-pattern emits.
 *
 * Auth-gated by Forge's proxy middleware just like every other /api/*.
 */

import { NextResponse } from 'next/server';
import { existsSync, readFileSync, statSync } from 'node:fs';
import { join, resolve, extname } from 'node:path';
import { getDataDir } from '@/lib/dirs';

const MIME: Record<string, string> = {
  '.md': 'text/markdown; charset=utf-8',
  '.txt': 'text/plain; charset=utf-8',
  '.json': 'application/json; charset=utf-8',
  '.yaml': 'application/yaml; charset=utf-8',
  '.yml': 'application/yaml; charset=utf-8',
  '.csv': 'text/csv; charset=utf-8',
  '.log': 'text/plain; charset=utf-8',
  '.html': 'text/html; charset=utf-8',
  '.pdf': 'application/pdf',
  '.png': 'image/png',
  '.jpg': 'image/jpeg',
  '.jpeg': 'image/jpeg',
  '.gif': 'image/gif',
  '.svg': 'image/svg+xml',
};

function isSensitiveTop(top: string): string | null {
  if (top.startsWith('.')) return 'dotfile';
  if (/\.(db|db-wal|db-shm)$/i.test(top)) return 'sqlite';
  if (/^forge\.log/i.test(top)) return 'log';
  if (/-tokens\.json$/i.test(top)) return 'token cache';
  return null;
}

export async function GET(
  req: Request,
  ctx: { params: Promise<{ path: string[] }> },
) {
  const { path: parts } = await ctx.params;
  if (!parts || parts.length === 0) {
    return NextResponse.json({ ok: false, error: 'missing path' }, { status: 400 });
  }
  for (const seg of parts) {
    if (seg === '..' || seg.startsWith('..')) {
      return NextResponse.json({ ok: false, error: 'path traversal rejected' }, { status: 400 });
    }
  }
  const top = parts[0]!;
  const blocked = isSensitiveTop(top);
  if (blocked) {
    return NextResponse.json({ ok: false, error: `top-level "${top}" is sensitive (${blocked})` }, { status: 403 });
  }
  const dataRoot = resolve(getDataDir());
  const target = resolve(join(dataRoot, ...parts));
  if (!target.startsWith(dataRoot + '/') && target !== dataRoot) {
    return NextResponse.json({ ok: false, error: 'path traversal rejected' }, { status: 400 });
  }
  if (!existsSync(target)) {
    return NextResponse.json({ ok: false, error: 'not found' }, { status: 404 });
  }
  const st = statSync(target);
  if (!st.isFile()) {
    return NextResponse.json({ ok: false, error: 'not a file' }, { status: 400 });
  }
  const url = new URL(req.url);
  const download = url.searchParams.get('download') === '1';
  const ext = extname(target).toLowerCase();
  const mime = MIME[ext] || 'application/octet-stream';
  const data = readFileSync(target);
  const filename = parts[parts.length - 1];
  return new NextResponse(new Uint8Array(data), {
    status: 200,
    headers: {
      'Content-Type': mime,
      'Content-Length': String(st.size),
      'Content-Disposition': `${download ? 'attachment' : 'inline'}; filename="${filename.replace(/"/g, '\\"')}"`,
      'Cache-Control': 'private, max-age=0, must-revalidate',
    },
  });
}
