package linthost

import (
  "bytes"
  "encoding/json"
  "os"
  "path/filepath"
  "testing"

  shimast "github.com/microsoft/typescript-go/shim/ast"
)

// TestFormatFixtureCorpus preserves the original fifteen canonical formatting
// inputs and exact authored outputs in one in-process compiler Program. Their
// original per-file format policies become scoped ConfigStore entries; no Node,
// Prettier process, installed launcher or plugin binary participates here.
// The separate write-command E2E matrix owns actual launcher publication.
//
// The current inputs already equal the expected main files. Ten historical
// dirty inputs and their authored outputs restore positive controls lost by
// repository formatting commits. These syntax-only cases share one parse-only
// matrix; the external Prettier comparison remains in its native E2E owner.
//
// @evidence contracts/testing.md#behavioral-verification One real Program and formatter engine select all eighteen sources of the fifteen original canonical cases under their original file-scoped options; canonical inputs permit no edit, then ten historical dirty inputs must converge to their independent authored outputs through the syntax-only engine; every expected main and untouched companion matches exactly.
// @evidence contracts/testing.md#independent-expectations The static corpus preserves the original source and separately authored expected bytes, including template interiors and line endings; ten dirty inputs and outputs retain their original Git revision provenance, and no expected text is generated by this engine.
// @evidence contracts/testing.md#distinguishing-cases Exactly fifteen named cases retain block, comments, JSDoc, mixed syntax, namespaces, nested blocks, width, quotes, semicolons, import order, statements, templates, commas and whitespace controls alongside ten historical positive controls, including original CRLF and trailing template bytes; empty or reduced populations fail.
// @evidence contracts/testing.md#execution-ownership The discoverable TestFormatFixtureCorpus creates one temporary source tree, parses original policies through collectConfigStore, loads one native compiler Program and runs one in-process engine cycle; then syntax-only historical positives share reparsed ASTs through the same engine without additional Programs or spawned producers. Case-named subtests compare every observation.
func TestFormatFixtureCorpus(t *testing.T) {
  fixture := filepath.Join("..", "test", "testdata", "format-corpus.json")
  original, err := os.ReadFile(fixture)
  if err != nil {
    t.Fatal(err)
  }
  var cases []struct {
    Name     string            `json:"name"`
    Format   map[string]any    `json:"format"`
    Sources  map[string]string `json:"sources"`
    Expected map[string]string `json:"expected"`
    Positive *struct {
      Source   string `json:"source"`
      Expected string `json:"expected"`
      Revision string `json:"revision"`
    } `json:"positive"`
  }
  if err := json.Unmarshal(original, &cases); err != nil {
    t.Fatal(err)
  }
  if len(cases) != 15 {
    t.Fatalf("original format population: got %d, want 15", len(cases))
  }
  root := t.TempDir()
  store := &ConfigStore{resolutionRoot: root}
  seen := make(map[string]bool)
  for _, c := range cases {
    if c.Name == "" || seen[c.Name] || filepath.Base(c.Name) != c.Name {
      t.Fatalf("invalid or duplicate case: %q", c.Name)
    }
    seen[c.Name] = true
    if len(c.Expected) != 1 || c.Expected["main.ts"] == "" || c.Sources["main.ts"] == "" {
      t.Fatalf("%s has no complete original oracle", c.Name)
    }
    for relative, source := range c.Sources {
      writeFile(t, filepath.Join(root, "src", c.Name, relative), source)
    }
    parsed, err := collectConfigStore(map[string]any{"files": []any{"src/" + c.Name + "/**/*"}, "format": c.Format}, root, "")
    if err != nil {
      t.Fatalf("%s config: %v", c.Name, err)
    }
    store.entries = append(store.entries, parsed.entries...)
  }
  writeFile(t, filepath.Join(root, "tsconfig.json"), `{"compilerOptions":{"target":"ES2022","module":"commonjs","strict":true,"noEmit":true,"rootDir":"src"},"include":["src/**/*"]}`)
  resolver, err := newFormatCommandResolver(store, root, "")
  if err != nil {
    t.Fatal(err)
  }
  engine := NewEngineWithResolver(resolver)
  if err := engine.ConfigError(); err != nil {
    t.Fatal(err)
  }
  engine.SetCurrentDirectory(root)
  prog, diagnostics, err := loadProgram(root, "tsconfig.json", loadProgramOptions{forceNoEmit: true, needsRuleChecker: engine.NeedsTypeChecker()})
  if prog != nil {
    defer prog.close()
  }
  if err != nil || len(diagnostics) != 0 || prog == nil {
    t.Fatalf("single format Program: err=%v diagnostics=%v", err, diagnostics)
  }
  if got := len(prog.projectSourceFiles()); got != 18 {
    t.Fatalf("original source population: got %d, want 18", got)
  }
  findings := prog.runWriteScopedCycle(engine)
  edits, err := applyFindingFixes(root, filterFormatFindings(findings))
  if err != nil {
    t.Error(err)
  }
  if edits != 0 {
    t.Errorf("canonical corpus received %d unexpected edits", edits)
  }
  for _, c := range cases {
    t.Run(c.Name, func(t *testing.T) {
      for relative, expected := range c.Expected {
        assertFileText(t, filepath.Join(root, "src", c.Name, relative), expected)
      }
      for relative, source := range c.Sources {
        assertFileText(t, filepath.Join(root, "src", c.Name, relative), source)
      }
    })
  }
  // Historical formatter fixtures were normalized by later repository format
  // commits. Preserve their authored dirty inputs and authored outputs too;
  // these formatter rules require syntax only, so they share one parse-only
  // file matrix without opening ten more compiler Programs.
  if engine.NeedsTypeChecker() {
    t.Fatal("format AST matrix unexpectedly needs a checker")
  }
  positives := 0
  for _, c := range cases {
    if c.Positive == nil {
      continue
    }
    positives++
    if c.Positive.Source == c.Positive.Expected || len(c.Positive.Revision) != 40 {
      t.Fatalf("%s historical positive has no independent dirty input/provenance", c.Name)
    }
    writeFile(t, filepath.Join(root, "src", c.Name, "main.ts"), c.Positive.Source)
  }
  if positives != 10 {
    t.Fatalf("historical positive population: got %d, want 10", positives)
  }
  converged := false
  totalEdits := 0
  passes := 0
  for pass := 0; pass < maxFormatPasses; pass++ {
    passes++
    var files []*shimast.SourceFile
    for _, c := range cases {
      for relative := range c.Sources {
        file := filepath.Join(root, "src", c.Name, relative)
        content, err := os.ReadFile(file)
        if err != nil {
          t.Fatal(err)
        }
        files = append(files, parseTSFile(t, file, string(content)))
      }
    }
    edits, err := applyFindingFixes(root, filterFormatFindings(engine.Run(files, nil)))
    if err != nil {
      t.Error(err)
      break
    }
    totalEdits += edits
    if edits == 0 {
      converged = true
      break
    }
  }
  if !converged || totalEdits == 0 {
    t.Errorf("historical positive AST matrix: converged=%v edits=%d", converged, totalEdits)
  }
  for _, c := range cases {
    t.Run(c.Name+"/historical", func(t *testing.T) {
      for relative, source := range c.Sources {
        expected := source
        if relative == "main.ts" && c.Positive != nil {
          expected = c.Positive.Expected
        }
        assertFileText(t, filepath.Join(root, "src", c.Name, relative), expected)
      }
    })
  }
  t.Logf("canonical cases=%d sources=18 compiler Programs=1; historical positives=%d parse-only passes=%d edits=%d", len(cases), positives, passes, totalEdits)
  after, err := os.ReadFile(fixture)
  if err != nil {
    t.Fatal(err)
  }
  if !bytes.Equal(after, original) {
    t.Error("static format corpus changed")
  }
}
