/**
 * Push3 → Solidity transpiler CLI entry point.
 *
 * Usage:  tsx src/index.ts <input.push3> <output.sol>
 *
 * Reads a Push3 program that consumes 8 dyadic rational inputs (slot 0 on top
 * of the DYADIC stack) and leaves 4 values at termination, then emits a
 * Solidity contract implementing calculateParams(OptimizerInput[8]).
 */

import * as fs from 'fs';
import * as path from 'path';
import { parse } from './parser.js';
import { transpile } from './transpiler.js';

const INT_LITERAL_RE = /^-?\d+$/;

/**
 * Validates that a transpiler output variable is a non-negative integer literal
 * (or a Solidity expression, which is unchecked).  Throws a clear error rather
 * than silently emitting `uint256(<negative>)` which Solidity would reject with a
 * cryptic compile-time message.
 */
function assertNonNegativeLiteral(value: string, name: string): void {
  if (INT_LITERAL_RE.test(value)) {
    const n = BigInt(value);
    if (n < 0n) {
      throw new Error(
        `Transpiler error: ${name} is a negative literal (${value}). ` +
        `uint256(${value}) is rejected by the Solidity compiler. ` +
        `Fix the Push3 program to produce a non-negative value for ${name}.`,
      );
    }
  }
}

/**
 * Validates that a transpiler output variable for a uint24 field is within the
 * valid range [0, 16777215] when it is a literal constant.  Large literals that
 * are computed at runtime are still wrapped with % (2**24) in the generated code.
 */
function assertUint24Range(value: string, name: string): void {
  if (INT_LITERAL_RE.test(value)) {
    const n = BigInt(value);
    if (n < 0n || n > 16777215n) {
      throw new Error(
        `Transpiler error: ${name} literal (${value}) is outside uint24 range [0, 16777215]. ` +
        `Fix the Push3 program to produce a value in [0, 16777215] for ${name}.`,
      );
    }
  }
}

/**
 * Validates that a transpiler output variable for a uint256 field scaled to 1e18
 * does not exceed 1e18 when it is a literal constant.  Values > 1e18 violate
 * LiquidityManager invariants (these fields represent fractions in [0, 1]).
 */
function assertUint256Max1e18(value: string, name: string): void {
  if (INT_LITERAL_RE.test(value)) {
    const n = BigInt(value);
    if (n > 1000000000000000000n) {
      throw new Error(
        `Transpiler error: ${name} literal (${value}) exceeds 1e18 (max allowed). ` +
        `Fix the Push3 program to produce a value in [0, 1e18] for ${name}.`,
      );
    }
  }
}

function main(): void {
  const args = process.argv.slice(2);
  if (args.length < 2) {
    console.error('Usage: tsx src/index.ts <input.push3> <output.sol>');
    process.exit(1);
  }

  const inputPath  = args[0];
  const outputPath = args[1];

  const src = fs.readFileSync(inputPath, 'utf8');
  console.log(`Parsing ${path.basename(inputPath)}...`);

  const ast = parse(src);
  console.log('Transpiling...');

  const { functionBody, ciVar, anchorShareVar, anchorWidthVar, discoveryDepthVar } = transpile(ast);

  assertNonNegativeLiteral(ciVar, 'ci');
  assertUint256Max1e18(ciVar, 'ci');
  assertNonNegativeLiteral(anchorShareVar, 'anchorShare');
  assertUint256Max1e18(anchorShareVar, 'anchorShare');
  assertUint24Range(anchorWidthVar, 'anchorWidth');
  assertNonNegativeLiteral(discoveryDepthVar, 'discoveryDepth');
  assertUint256Max1e18(discoveryDepthVar, 'discoveryDepth');

  const solidityLines = [
    '// SPDX-License-Identifier: GPL-3.0-or-later',
    'pragma solidity ^0.8.19;',
    '',
    'import {OptimizerInput} from "./IOptimizer.sol";',
    '',
    '/**',
    ' * @title OptimizerV3Push3',
    ' * @notice Auto-generated from optimizer_v3.push3 via Push3→Solidity transpiler.',
    ' *         Implements calculateParams with 8 dyadic rational inputs and 4 outputs.',
    ' */',
    'contract OptimizerV3Push3 {',
    '    /**',
    '     * @notice Compute liquidity parameters from 8 dyadic rational inputs.',
    '     * @param inputs  8-slot dyadic rational array: slot 0 = percentageStaked (top of Push3 stack),',
    '     *                slot 1 = averageTaxRate, slots 2-7 = extended metrics (0 if unavailable).',
    '     * @return ci              Capital inefficiency (0..1e18).',
    '     * @return anchorShare     Fraction of non-floor ETH in anchor (0..1e18).',
    '     * @return anchorWidth     Anchor position width in tick units.',
    '     * @return discoveryDepth  Discovery liquidity density (0..1e18).',
    '     */',
    '    function calculateParams(OptimizerInput[8] memory inputs)',
    '        public',
    '        pure',
    '        returns (uint256 ci, uint256 anchorShare, uint24 anchorWidth, uint256 discoveryDepth)',
    '    {',
    '        // Validate mantissa for percentageStaked',
    '        require(inputs[0].mantissa <= 1e18, "mantissa overflow");',
    '',
    '        // Validate that shift is 0 (future-only field, not yet supported)',
    '        for (uint256 k = 0; k < 8; k++) {',
    '            require(inputs[k].shift == 0, "shift not yet supported");',
    '        }',
    '',
    '        // Layer A: bear defaults — any output not overwritten by the program keeps these.',
    '        // Matches Push3 no-op semantics: a program that crashes or produces no output',
    '        // returns safe bear-mode parameters rather than reverting.',
    '        ci = 0;',
    '        anchorShare = 300000000000000000;',
    '        anchorWidth = 100;',
    '        discoveryDepth = 300000000000000000;',
    '',
    '        // Layer C: unchecked arithmetic — overflow wraps (matches Push3 semantics).',
    '        // Division by zero is guarded at the expression level (b == 0 ? 0 : a / b).',
    '        unchecked {',
    ...functionBody,
    `        ci = uint256(${ciVar});`,
    `        anchorShare = uint256(${anchorShareVar});`,
    `        anchorWidth = uint24(${anchorWidthVar} % (2**24));`,
    `        discoveryDepth = uint256(${discoveryDepthVar});`,
    '        }',
    '    }',
    '}',
    '',
  ];

  const output = solidityLines.join('\n');
  fs.writeFileSync(outputPath, output, 'utf8');
  console.log(`Written: ${outputPath}`);

  // Print a summary
  console.log(`  Function body: ${functionBody.length} lines`);
  console.log(`  Outputs:       ci=${ciVar}, anchorShare=${anchorShareVar}, anchorWidth=${anchorWidthVar}, discoveryDepth=${discoveryDepthVar}`);
}

main();
