import fssync from 'node:fs';
import path from 'node:path';

import yauzl from 'yauzl';

import type { Project } from '../../shared/types';

export function unzipToDir(
  zipPath: string,
  outDir: string,
  onProgress?: (done: number, total: number) => void,
): Promise<void> {
  return new Promise((resolve, reject) => {
    yauzl.open(zipPath, { lazyEntries: true }, (err, zip) => {
      if (err) return reject(err);
      const zipFile = zip;
      let settled = false;
      const total = zipFile.entryCount || 0;
      let done = 0;

      const safeClose = (): void => {
        try {
          zipFile.close();
        } catch {
          /* ignore */
        }
      };

      const finishOk = (): void => {
        if (settled) return;
        settled = true;
        safeClose();
        resolve();
      };

      const finishErr = (e: unknown): void => {
        if (settled) return;
        settled = true;
        safeClose();
        reject(e instanceof Error ? e : new Error(String(e)));
      };

      zipFile.readEntry();
      zipFile.on('entry', (entry: yauzl.Entry) => {
        if (settled) return;
        done += 1;
        if (onProgress && total > 0) {
          try {
            onProgress(done, total);
          } catch {
            // ignore
          }
        }
        const filePath = path.join(outDir, entry.fileName);
        if (entry.fileName.endsWith('/')) {
          fssync.mkdirSync(filePath, { recursive: true });
          zipFile.readEntry();
          return;
        }
        fssync.mkdirSync(path.dirname(filePath), { recursive: true });
        zipFile.openReadStream(entry, (streamErr, readStream) => {
          if (streamErr) return finishErr(streamErr);
          readStream.on('error', finishErr);
          const writeStream = fssync.createWriteStream(filePath);
          writeStream.on('error', finishErr);
          readStream.pipe(writeStream);
          writeStream.on('close', () => {
            if (!settled) zipFile.readEntry();
          });
        });
      });
      zipFile.on('end', () => finishOk());
      zipFile.on('error', finishErr);
    });
  });
}

/** Читает `project.json` из zip; всегда закрывает дескриптор yauzl (иначе на Windows возможен EMFILE). */
export async function readProjectJsonFromZip(zipPath: string): Promise<Project> {
  return new Promise<Project>((resolve, reject) => {
    yauzl.open(zipPath, { lazyEntries: true }, (err, zip) => {
      if (err) return reject(err);
      const zipFile = zip;
      let settled = false;

      const safeClose = (): void => {
        try {
          zipFile.close();
        } catch {
          /* ignore */
        }
      };

      const finishOk = (project: Project): void => {
        if (settled) return;
        settled = true;
        safeClose();
        resolve(project);
      };

      const finishErr = (e: unknown): void => {
        if (settled) return;
        settled = true;
        safeClose();
        reject(e instanceof Error ? e : new Error(String(e)));
      };

      zipFile.readEntry();
      zipFile.on('entry', (entry: yauzl.Entry) => {
        if (settled) return;
        if (entry.fileName !== 'project.json') {
          zipFile.readEntry();
          return;
        }
        zipFile.openReadStream(entry, (streamErr, readStream) => {
          if (streamErr) return finishErr(streamErr);
          const chunks: Buffer[] = [];
          readStream.on('data', (c: Buffer) => chunks.push(c));
          readStream.on('error', finishErr);
          readStream.on('end', () => {
            try {
              const raw = Buffer.concat(chunks).toString('utf8');
              const parsed = JSON.parse(raw) as unknown as Project;
              finishOk(parsed);
            } catch (e) {
              finishErr(e instanceof Error ? e : new Error('Failed to parse project.json'));
            }
          });
        });
      });
      zipFile.on('error', finishErr);
      zipFile.on('end', () => {
        finishErr(new Error('project.json not found in zip'));
      });
    });
  });
}
