import * as esbuild from 'esbuild';
import * as fs from 'node:fs';
import http from 'node:http';
import path from 'node:path';
import { JSDOM } from 'jsdom';

const __dirname = path.dirname(new URL(import.meta.url).pathname);

const colors = {
  reset: '\x1b[0m',
  dim: '\x1b[2m',
  bright: '\x1b[1m',
  normal: '\x1b[22m',
  red: '\x1b[31m',
  green: '\x1b[32m',
  yellow: '\x1b[33m',
};

const mime = {
  '.svg': 'image/svg+xml; charset=utf-8',
  '.png': 'image/png',
  '.jpg': 'image/jpeg',
  '.css': 'text/css; charset=utf-8',
  '.html': 'text/html; charset=utf-8',
  '.js': 'text/javascript; charset=utf-8',
};

function createServerSentEventHandler() {
  const listeners = new Set();
  const setupConnection = (req, res) => {
    // res.writeHead(200, {
    //   'Content-Type': 'text/event-stream',
    //   'Cache-Control': 'no-cache',
    //   Connection: 'keep-alive',
    // });
    listeners.add(res);
    // req.on('close', () => {
    //   listeners.delete(res);
    // });
  };

  const sendMessage = (data) => {
    listeners.forEach((res) => {
      res.write(`data: ${JSON.stringify(data)}\n\n`);
    });
  };

  return { setupConnection, sendMessage };
}

const buildListeners = createServerSentEventHandler();

const buildStatusPlugin = {
  name: 'build-status',
  setup(build) {
    let buildStart = Date.now();
    let buildResolver = () => {};
    let buildPromise = Promise.resolve();

    build.onStart(() => {
      buildStart = Date.now();
      buildPromise = new Promise((resolve) => {
        buildResolver = resolve;
      });
      buildListeners.sendMessage({ type: 'build-start' });
    });
    build.onEnd((result) => {
      const duration = Date.now() - buildStart;
      buildStart = -1;
      buildResolver();
      const success = result.errors.length === 0;
      buildListeners.sendMessage({ type: 'build-end', duration, success });
    });
  },
};

const importSvg = {
  name: 'importSvg',
  setup(build) {
    const regex = new RegExp(/\.svg$/);

    build.onResolve({ filter: /\*\.svg$/ }, args => {
      console.log('onresolve', args);

      return {
        path: path.join('public', 'assets', args.path),
        namespace: 'svg-stub'
      }
    });

    build.onLoad({ filter: /\.svg$/, namespace: 'svg-stub' }, async (args) => {
      console.log('onload', args);

      const svgs = fs.readdirSync(path.resolve(path.dirname(args.path))).filter(fn => regex.test(fn));;

      console.log(svgs);

      console.log('resolved path', path.join(path.dirname(args.path), 'mapsheets.svg'));
      console.log('cwd', process.cwd());

      // const contents = `import mapsheets from ./${path.join(path.dirname(args.path), 'mapsheets.svg')};
      //   console.log('mapsheets', mapsheets);
      //   export default mapsheets;`;

      // const contents = `
      //   import svg from '/usr/src/app/public/assets/images/scenario-side_show.svg';
      //   export default svg;
      // `;

      // const contents = `
      //   import svg from '/usr/src/app/public/assets/images/scenario-side_show.svg';
      //   export default svg;
      // `;

      // const contents = `
      //   export { default as scenario_sideShow } from '/usr/src/app/public/assets/images/scenario-side_show.svg';
      //   export { default as mapsheets } from '/usr/src/app/public/assets/images/mapsheets.svg';
      // `;

      const contents = `
        // export { default as countorLines } from './contour-lines.svg';
        export { default as mapsheets } from './mapsheets.svg';
      `;

      console.log('resolveDir', path.dirname(args.path));
      console.log('contents', contents);

      return {
        contents: contents,
        resolveDir: path.dirname(args.path) //'./public/assets/images'
      }
    });
  }
};

const svgUseCacheBust = {
  name: 'svgUseCacheBust',
  setup(build) {
    const regex = new RegExp('mapsheets\..+\.svg');

    build.onStart(() => {
      console.log("BUILD START");

      // const version = Date.now();
      // const file = fs.readFileSync('./src/scenario-side_show.svg', { encoding: 'utf-8' });
      // const newFile = file.replaceAll('%%VERSION%%', version);
      // fs.writeFileSync('./public/assets/images/scenario-side_show.svg', newFile);
      // const files = fs.readdirSync('./public/assets/images').filter(fn => regex.test(fn));
      // files.forEach(fn => fs.unlinkSync(`./public/assets/images/${fn}`));
      // fs.copyFileSync('./public/assets/images/mapsheets.svg', `./public/assets/images/mapsheets.${version}.svg`);
    })
  }
};

const resolveSvgImports = {
  name: 'resolveSvgImports',
  setup(build) {
    let buildStart;

    build.onStart(() => {
      buildStart = Date.now();
      console.log('Build started');
      fs.rmSync(path.resolve(build.initialOptions.outdir), { recursive: true, force: true });
    });

    build.onResolve({ filter: /\.svg$/ }, args => {
      return {
        path: path.resolve('public', args.path),
      };
    });

    build.onEnd(() => {
      console.log(`Build completed in ${Date.now() - buildStart}ms`);
    });
  }
}

const externalSvgToInternal = {
  name: 'externalSvgToInternal',
  setup(build) {
    build.onLoad({ filter: /\.svg$/ }, async (args) => {
      const document = (await JSDOM.fromFile(args.path)).window.document;
      const externalResourceEls = Array.from(document.querySelectorAll('use[href*=".svg"'));

      const refs = externalResourceEls.reduce((acc, el) => {
        const href = el.getAttributeNS(null, 'href');
        const [filename] = href.match(/.+\.svg/);
        const fragmentIdentifier = href.split('.svg').pop();

        (acc[filename] ??= new Set()).add(fragmentIdentifier);
        el.setAttributeNS(null, 'href', fragmentIdentifier);

        return acc;
      }, {});

      await Promise.all(
        Object.keys(refs).map(filename => JSDOM.fromFile(path.join(path.dirname(args.path), filename)))
      ).then(result => {
        const defs = document.querySelector('defs');

        Object.keys(refs).forEach((filename, index) => {
          const external = result[index].window.document;

          refs[filename].forEach(fragmentIdentifier => {
            external
              .querySelectorAll(`${fragmentIdentifier} use`)
              .forEach(el => refs[filename].add(el.getAttributeNS(null, 'href')));
          });

          const refsQuery = [...refs[filename]].join(', ');
          external.querySelectorAll(refsQuery).forEach(node => defs.append(node));
        });
      });

      return {
        contents: `<?xml version="1.0" standalone="no"?>\n${document.querySelector('svg').outerHTML}`,
        loader: 'file',
        watchFiles: Object.keys(refs).map(filename => path.join(path.dirname(args.path), filename))
      }
    });
  }
}

const buildOptions = {
  entryPoints: ['src/index.js', 'src/soldier_record_block.js', 'src/map.js'],
  bundle: true,
  outdir: 'build',
  plugins: [
    resolveSvgImports,
    // externalSvgToInternal,
    // buildStatusPlugin
  ],
  loader: {
    '.svg': 'file'
  },
  assetNames: 'assets/images/[name]-[hash]',
};

if (process.env.NODE_ENV === 'test') {
  http.createServer((req, res) => {
    const filename = req.url && req.url !== '/' ? req.url : 'index.html';
    const filepath = ['public', 'build'].map(dir => path.join(__dirname, dir, filename)).find(fp => fs.existsSync(fp));

    if (filepath) {
      const readStream = fs.createReadStream(filepath, { autoClose: true });

      readStream.on('ready', () => {
        const type = mime[path.parse(filepath).ext] || 'text/plain';
        res.writeHead(200, { 'content-type': type });
        readStream.pipe(res, { end: true });
      });

      readStream.on('error', err => {
        res.writeHead(500, err.name);
        res.end(JSON.stringify(err));
      });
    } else {
      res.writeHead(404, { 'content-type': 'text/plain' });
      res.end("Not found");
    }
  }).listen(3005, () => {
    const serverUrl = 'http://localhost:3005';
    console.log(`Test server running at ${serverUrl}`);
  });
} else {
  buildOptions.define = { 'window.IS_DEV': 'true' };
  const ctx = await esbuild.context(buildOptions);
  await ctx.watch();

  const { host, port } = await ctx.serve({
    servedir: 'build',
    port: 3000,
    // onRequest: function({ remoteAddress, method, path, status, timeInMS }) {
    //   let statusColor = colors.red;

    //   if (status >= 200 && status <= 299) {
    //     statusColor = colors.green;
    //   } else if (status >= 300 && status <= 399) {
    //     statusColor = colors.yellow;
    //   }

    //   console.log(`${colors.dim}${remoteAddress} - "${method} ${path}" ${colors.normal}${statusColor}${status}${colors.reset}${colors.dim} [${timeInMS}ms]${colors.reset}`);
    // },
  });

  http.createServer((req, res) => {
    const options = {
      hostname: host,
      port: port,
      path: req.url,
      method: req.method,
      headers: req.headers,
    }

    const filename = req.url && req.url !== '/' ? req.url : 'index.html';
    const filepath = path.join(__dirname, 'public', filename);

    if (fs.existsSync(filepath)) {
      const readStream = fs.createReadStream(filepath, { autoClose: true });

      readStream.on('ready', () => {
        const type = mime[path.parse(filepath).ext] || 'application/octet-stream';
        console.log(`${req.method} ${req.url} => 200 ${type}`);
        res.writeHead(200, { 'content-type': type });
        readStream.pipe(res, { end: true });
      });

      readStream.on('error', err => {
        console.log(`${req.method} ${req.url} => 500 ${filepath} ${err.name}`);
        res.writeHead(500, err.name);
        res.end(JSON.stringify(err));
      });
    } else {
      const proxyReq = http.request(options, proxyRes => {
        const type = proxyRes.headers['content-type'];
        console.log(`${req.method} ${req.url} => ${proxyRes.statusCode} ${type} via esbuild`);
        res.writeHead(proxyRes.statusCode, { 'content-type': type });
        // if (req.url === '/esbuild') buildListeners.setupConnection(req, res);
        proxyRes.pipe(res);
      });

      req.pipe(proxyReq, { end: true });
    }
  }).listen(8080, (e) => {
    const serverUrl = 'http://localhost:8080';
    console.log(`Development server running at ${serverUrl}`);
  });
}

// console.log(`${process.env.NODE_ENV === 'test' ? 'Test' : 'Development'} server running at ${server.url}`);
