import fs from 'node:fs/promises';

import { session } from 'electron';

import { asAssetId, asPlayerId, asTokenId } from '../../shared/types/ids';
import type { PlayersStore } from '../players/playersStore';
import type { ZipProjectStore } from '../project/zipStore';
import type { TokensStore } from '../tokens/tokensStore';

type ReadInfo = { absPath: string; mime: string };

async function serveFile(info: ReadInfo, request: Request): Promise<Response> {
  try {
    const stat = await fs.stat(info.absPath);
    const total = stat.size;
    const range = request.headers.get('range') ?? request.headers.get('Range');

    if (range) {
      const m = /^bytes=(\d+)-(\d+)?$/iu.exec(range.trim());
      if (m) {
        const start = Number(m[1]);
        const endRaw = m[2] ? Number(m[2]) : total - 1;
        const end = Math.min(endRaw, total - 1);
        if (!Number.isFinite(start) || !Number.isFinite(end) || start < 0 || start >= total || end < start) {
          return new Response(null, {
            status: 416,
            headers: {
              'Content-Range': `bytes */${String(total)}`,
              'Cache-Control': 'no-store',
            },
          });
        }
        const len = end - start + 1;
        const fh = await fs.open(info.absPath, 'r');
        try {
          const buf = Buffer.alloc(len);
          const { bytesRead } = await fh.read(buf, 0, len, start);
          if (bytesRead <= 0) {
            return new Response(null, {
              status: 416,
              headers: {
                'Content-Range': `bytes */${String(total)}`,
                'Cache-Control': 'no-store',
              },
            });
          }
          const body = bytesRead === len ? buf : Buffer.from(buf.subarray(0, bytesRead));
          const actualEnd = start + bytesRead - 1;
          return new Response(body, {
            status: 206,
            headers: {
              'Content-Type': info.mime,
              'Accept-Ranges': 'bytes',
              'Content-Range': `bytes ${String(start)}-${String(actualEnd)}/${String(total)}`,
              'Content-Length': String(body.length),
              'Cache-Control': 'no-store',
            },
          });
        } finally {
          await fh.close();
        }
      }
    }

    const buf = await fs.readFile(info.absPath);
    return new Response(buf, {
      headers: {
        'Content-Type': info.mime,
        'Accept-Ranges': 'bytes',
        'Cache-Control': 'no-store',
      },
    });
  } catch {
    return new Response(null, { status: 404 });
  }
}

/**
 * Обслуживает `dnd://asset?...`, `dnd://token?...` и `dnd://player?...`.
 */
export function registerDndAssetProtocol(
  projectStore: ZipProjectStore,
  tokensStore: TokensStore,
  playersStore: PlayersStore | null,
): void {
  session.defaultSession.protocol.handle('dnd', async (request) => {
    const url = new URL(request.url);
    const id = url.searchParams.get('id');
    if (!id) {
      return new Response(null, { status: 404 });
    }
    let info: ReadInfo | null = null;
    if (url.hostname === 'asset') {
      info = projectStore.getAssetReadInfo(asAssetId(id));
    } else if (url.hostname === 'token') {
      info = tokensStore.getImageReadInfo(asTokenId(id));
    } else if (url.hostname === 'player') {
      info = playersStore?.getImageReadInfo(asPlayerId(id)) ?? null;
    }
    if (!info) {
      return new Response(null, { status: 404 });
    }
    return serveFile(info, request);
  });
}
