/**
 * GSD Tools Tests - settings.json JSONC (JSON with comments) support
 *
 * Validates that the installer's readSettings() correctly handles
 * settings.json files containing comments (line and block) without
 * silently overwriting them with empty objects.
 *
 * Closes: #1461
 */

const { test, describe, beforeEach, afterEach } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');

// ─── inline stripJsonComments (mirrors install.js logic) ─────────────────────

function stripJsonComments(text) {
  let result = '';
  let i = 0;
  let inString = false;
  let stringChar = '';
  while (i < text.length) {
    if (inString) {
      if (text[i] === '\\') {
        result += text[i] + (text[i + 1] || '');
        i += 2;
        continue;
      }
      if (text[i] === stringChar) {
        inString = false;
      }
      result += text[i];
      i++;
      continue;
    }
    if (text[i] === '"' || text[i] === "'") {
      inString = true;
      stringChar = text[i];
      result += text[i];
      i++;
      continue;
    }
    if (text[i] === '/' && text[i + 1] === '/') {
      while (i < text.length && text[i] !== '\n') i++;
      continue;
    }
    if (text[i] === '/' && text[i + 1] === '*') {
      i += 2;
      while (i < text.length && !(text[i] === '*' && text[i + 1] === '/')) i++;
      i += 2;
      continue;
    }
    result += text[i];
    i++;
  }
  return result.replace(/,\s*([}\]])/g, '$1');
}

// ─── tests ───────────────────────────────────────────────────────────────────

describe('stripJsonComments (#1461)', () => {

  test('strips line comments', () => {
    const input = `{
  // This is a comment
  "key": "value"
}`;
    const result = JSON.parse(stripJsonComments(input));
    assert.deepStrictEqual(result, { key: 'value' });
  });

  test('strips block comments', () => {
    const input = `{
  /* Block comment */
  "key": "value"
}`;
    const result = JSON.parse(stripJsonComments(input));
    assert.deepStrictEqual(result, { key: 'value' });
  });

  test('strips multi-line block comments', () => {
    const input = `{
  /*
   * Multi-line
   * block comment
   */
  "key": "value"
}`;
    const result = JSON.parse(stripJsonComments(input));
    assert.deepStrictEqual(result, { key: 'value' });
  });

  test('preserves comments inside string values', () => {
    const input = `{
  "url": "https://example.com/path",
  "description": "Use // for line comments"
}`;
    const result = JSON.parse(stripJsonComments(input));
    assert.strictEqual(result.url, 'https://example.com/path');
    assert.strictEqual(result.description, 'Use // for line comments');
  });

  test('handles trailing commas', () => {
    const input = `{
  "a": 1,
  "b": 2,
}`;
    const result = JSON.parse(stripJsonComments(input));
    assert.deepStrictEqual(result, { a: 1, b: 2 });
  });

  test('handles inline comments after values', () => {
    const input = `{
  "timeout": 5000, // milliseconds
  "retries": 3 // max attempts
}`;
    const result = JSON.parse(stripJsonComments(input));
    assert.strictEqual(result.timeout, 5000);
    assert.strictEqual(result.retries, 3);
  });

  test('handles standard JSON (no comments) unchanged', () => {
    const input = '{"key": "value", "num": 42}';
    const result = JSON.parse(stripJsonComments(input));
    assert.deepStrictEqual(result, { key: 'value', num: 42 });
  });

  test('handles empty object', () => {
    const result = JSON.parse(stripJsonComments('{}'));
    assert.deepStrictEqual(result, {});
  });

  test('handles real-world settings.json with comments', () => {
    const input = `{
  // My configuration
  "hooks": {
    "SessionStart": [
      {
        "matcher": "", /* match all */
        "hooks": [
          {
            "type": "command",
            "command": "node ~/.claude/hooks/gsd-statusline.js"
          }
        ]
      }
    ]
  },
  "statusLine": {
    "command": "node ~/.claude/hooks/gsd-statusline.js",
    "refreshInterval": 10
  }
}`;
    const result = JSON.parse(stripJsonComments(input));
    assert.ok(result.hooks, 'should have hooks');
    assert.ok(result.statusLine, 'should have statusLine');
    assert.strictEqual(result.statusLine.refreshInterval, 10);
  });
});

describe('readSettings null return on malformed files (#1461)', () => {
  test('install.js contains JSONC stripping in readSettings', () => {
    const installPath = path.join(__dirname, '..', 'bin', 'install.js');
    const content = fs.readFileSync(installPath, 'utf8');
    assert.ok(content.includes('stripJsonComments'),
      'install.js should use stripJsonComments in readSettings');
  });

  test('readSettings returns null on truly malformed files (not empty object)', () => {
    const installPath = path.join(__dirname, '..', 'bin', 'install.js');
    const content = fs.readFileSync(installPath, 'utf8');
    assert.ok(content.includes('return null'),
      'readSettings should return null on parse failure, not empty object');
  });

  test('callers guard against null readSettings return', () => {
    const installPath = path.join(__dirname, '..', 'bin', 'install.js');
    const content = fs.readFileSync(installPath, 'utf8');
    // Should have null guards at the settings configuration call sites
    assert.ok(
      content.includes('=== null') || content.includes('rawSettings === null'),
      'callers should check for null return from readSettings'
    );
  });
});
