const fs = require('fs');
const path = require('path');

const rootDir = path.resolve(__dirname, '..');
const defaultManifestPath = path.join(rootDir, 'dist-electron', 'release-manifest.json');
const descriptorCandidates = ['latest.yml', 'latest-mac.yml', 'latest-linux.yml'];

function normalizeVersion(rawValue) {
  if (typeof rawValue !== 'string') {
    return null;
  }

  const trimmedValue = rawValue.trim();

  if (!trimmedValue) {
    return null;
  }

  return trimmedValue.replace(/^v/i, '').split('+')[0] || null;
}

function parseVersion(rawValue) {
  const normalized = normalizeVersion(rawValue);

  if (!normalized) {
    return null;
  }

  const match = normalized.match(/^(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:-([0-9A-Za-z.-]+))?$/);

  if (!match) {
    return null;
  }

  return {
    major: Number.parseInt(match[1], 10),
    minor: Number.parseInt(match[2] || '0', 10),
    patch: Number.parseInt(match[3] || '0', 10),
    prerelease: match[4] ? match[4].split('.') : []
  };
}

function comparePrereleaseIdentifiers(left, right) {
  const leftNumeric = /^\d+$/.test(left) ? Number.parseInt(left, 10) : null;
  const rightNumeric = /^\d+$/.test(right) ? Number.parseInt(right, 10) : null;

  if (leftNumeric !== null && rightNumeric !== null) {
    return leftNumeric - rightNumeric;
  }

  if (leftNumeric !== null) {
    return -1;
  }

  if (rightNumeric !== null) {
    return 1;
  }

  return left.localeCompare(right);
}

function compareVersions(leftValue, rightValue) {
  const left = parseVersion(leftValue);
  const right = parseVersion(rightValue);

  if (!left && !right) {
    return 0;
  }

  if (!left) {
    return -1;
  }

  if (!right) {
    return 1;
  }

  if (left.major !== right.major) {
    return left.major - right.major;
  }

  if (left.minor !== right.minor) {
    return left.minor - right.minor;
  }

  if (left.patch !== right.patch) {
    return left.patch - right.patch;
  }

  if (left.prerelease.length === 0 && right.prerelease.length === 0) {
    return 0;
  }

  if (left.prerelease.length === 0) {
    return 1;
  }

  if (right.prerelease.length === 0) {
    return -1;
  }

  const maxLength = Math.max(left.prerelease.length, right.prerelease.length);

  for (let index = 0; index < maxLength; index += 1) {
    const leftIdentifier = left.prerelease[index];
    const rightIdentifier = right.prerelease[index];

    if (!leftIdentifier) {
      return -1;
    }

    if (!rightIdentifier) {
      return 1;
    }

    const comparison = comparePrereleaseIdentifiers(leftIdentifier, rightIdentifier);

    if (comparison !== 0) {
      return comparison;
    }
  }

  return 0;
}

function parseArgs(argv) {
  const args = {};

  for (let index = 0; index < argv.length; index += 1) {
    const token = argv[index];

    if (!token.startsWith('--')) {
      continue;
    }

    const key = token.slice(2);
    const nextToken = argv[index + 1];

    if (!nextToken || nextToken.startsWith('--')) {
      args[key] = true;
      continue;
    }

    args[key] = nextToken;
    index += 1;
  }

  return args;
}

function ensureHttpUrl(rawValue) {
  if (typeof rawValue !== 'string' || rawValue.trim().length === 0) {
    throw new Error('A release feed URL is required. Pass it with --feed-url.');
  }

  const parsedUrl = new URL(rawValue.trim());

  if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') {
    throw new Error('The release feed URL must use http:// or https://.');
  }

  return parsedUrl.toString().replace(/\/$/, '');
}

function readJsonIfExists(filePath) {
  if (!fs.existsSync(filePath)) {
    return null;
  }

  return JSON.parse(fs.readFileSync(filePath, 'utf8'));
}

function warnIfNoDescriptors(distPath) {
  const availableDescriptors = descriptorCandidates.filter((fileName) => {
    return fs.existsSync(path.join(distPath, fileName));
  });

  if (availableDescriptors.length === 0) {
    console.warn('[release-manifest] Warning: no latest*.yml descriptor was found in dist-electron/.');
    console.warn('[release-manifest] Upload a feed directory that contains the Electron Builder update descriptors.');
  }
}

function buildDefaultManifest(version) {
  return {
    schemaVersion: 1,
    generatedAt: new Date().toISOString(),
    minimumServerVersion: version,
    pollIntervalMinutes: 30,
    versions: []
  };
}

function sortManifestVersions(versions) {
  return [...versions].sort((left, right) => compareVersions(right.version, left.version));
}

function main() {
  const args = parseArgs(process.argv.slice(2));
  const packageJson = JSON.parse(fs.readFileSync(path.join(rootDir, 'package.json'), 'utf8'));
  const version = normalizeVersion(args.version || packageJson.version);

  if (!version) {
    throw new Error('Unable to determine the release version. Pass it with --version.');
  }

  const feedUrl = ensureHttpUrl(args['feed-url']);
  const manifestPath = path.resolve(rootDir, args.manifest || defaultManifestPath);
  const existingPath = path.resolve(rootDir, args.existing || manifestPath);
  const existingManifest = readJsonIfExists(existingPath) || buildDefaultManifest(version);
  const distPath = path.join(rootDir, 'dist-electron');

  if (fs.existsSync(distPath)) {
    warnIfNoDescriptors(distPath);
  }

  const nextEntry = {
    version,
    feedUrl,
    publishedAt: typeof args['published-at'] === 'string'
      ? args['published-at']
      : new Date().toISOString(),
    ...(typeof args.notes === 'string' && args.notes.trim().length > 0
      ? { notes: args.notes.trim() }
      : {})
  };
  const nextManifest = {
    schemaVersion: 1,
    generatedAt: new Date().toISOString(),
    minimumServerVersion: normalizeVersion(args['minimum-server-version'])
      || normalizeVersion(existingManifest.minimumServerVersion)
      || version,
    pollIntervalMinutes: Number.isFinite(Number(args['poll-interval-minutes']))
      ? Math.max(5, Math.round(Number(args['poll-interval-minutes'])))
      : Math.max(5, Math.round(Number(existingManifest.pollIntervalMinutes || 30))),
    versions: sortManifestVersions(
      [...(Array.isArray(existingManifest.versions) ? existingManifest.versions : [])]
        .filter((entry) => normalizeVersion(entry && entry.version) !== version)
        .concat(nextEntry)
    )
  };

  fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
  fs.writeFileSync(manifestPath, `${JSON.stringify(nextManifest, null, 2)}\n`, 'utf8');

  console.log(`[release-manifest] Wrote ${manifestPath}`);
}

try {
  main();
} catch (error) {
  const message = error instanceof Error ? error.message : String(error);

  console.error(`[release-manifest] ${message}`);
  process.exitCode = 1;
}
