/-
Copyright (c) 2026 SilverSight Contributors. All rights reserved.
Released under Apache 2.0 license.

Generic n-dimensional spectral profile.
Unlike the original Spectral.lean (which fixed n=8), this version
carries the matrix dimension as a type parameter (n : Nat).
-/

import SilverSight.FixedPoint
import SilverSight.PIST.MatrixN

namespace SilverSight.PIST.SpectralN

open SilverSight.FixedPoint
open SilverSight.FixedPoint.Q16_16
open SilverSight.PIST.MatrixN

/-- Spectral profile for an n×n matrix. The dimension n is tracked at the type level. -/
structure SpectralProfile (n : Nat) where
  matrix_size             : Q16_16  -- = ofRawInt (n : Int)
  rank                    : Q16_16
  spectral_gap            : Q16_16
  density                 : Q16_16
  trace_val               : Q16_16
  frobenius_norm          : Q16_16
  laplacian_zero_count    : Q16_16
  adjacency_eigenvalue_max : Q16_16
  laplacian_eigenvalue_max : Q16_16
  singular_value_max      : Q16_16
  deriving Repr

/-- Empty profile (for n = 0). -/
def emptyProfile (n : Nat) : SpectralProfile n :=
  { matrix_size             := zero
    rank                    := zero
    spectral_gap            := zero
    density                 := zero
    trace_val               := zero
    frobenius_norm          := zero
    laplacian_zero_count    := zero
    adjacency_eigenvalue_max := zero
    laplacian_eigenvalue_max := zero
    singular_value_max      := zero }

/-- Compute the full spectral profile of an n×n Int matrix. -/
def computeSpectral (n : Nat) (mat : Array (Array Int)) : SpectralProfile n :=
  if n = 0 then emptyProfile n
  else
    let sym := symmetrize mat n
    let lap := buildLaplacian sym n

    let evMax := powerIteration mat n

    -- Second eigenvalue via shift-deflation
    let shiftAmt : Int := (evMax.toInt * 58982) / q16Scale
    let shiftedMat : Array (Array Int) :=
      Array.ofFn (n := n) fun i =>
        Array.ofFn (n := n) fun j =>
          let base := getEntry sym i.val j.val
          if i.val = j.val then base - shiftAmt else base
    let evShift := powerIteration shiftedMat n
    let evSecond : Q16_16 :=
      if evShift.toInt < evMax.toInt
      then ofRawInt (Int.natAbs (evMax.toInt - evShift.toInt) : Int)
      else evMax
    let spectralGap := sub evMax evSecond

    let lapMax := powerIteration lap n

    let total : Int := mat.foldl (fun acc row => acc + row.foldl (· + ·) 0) 0
    let nSqI : Int := (n * n : Nat)
    let density := if nSqI ≤ 0 then zero else ofRawInt (total * q16Scale / nSqI)

    let traceInt : Int := (List.range n).foldl (fun acc i => acc + getEntry mat i i) 0
    let traceVal := ofRawInt traceInt

    let frobSq : Int := mat.foldl (fun acc row => acc + row.foldl (fun a c => a + c * c) 0) 0
    let frobNorm := ofRawInt (isqrt frobSq)

    let rankVal : Int := mat.foldl (fun acc row =>
      acc + if row.any (· ≠ 0) then 1 else 0) 0

    let lapZero : Int := (List.range n).foldl (fun acc i =>
      let rs := rowSum mat i n
      let d  := getEntry mat i i
      acc + if rs - d = 0 then 1 else 0) 0

    let ataM := buildATA mat n
    let ataEv := powerIteration ataM n
    let svMax := if ataEv.toInt > 0 then sqrt ataEv else zero

    { matrix_size             := ofRawInt (n : Int)
      rank                    := ofRawInt rankVal
      spectral_gap            := spectralGap
      density                 := density
      trace_val               := traceVal
      frobenius_norm          := frobNorm
      laplacian_zero_count    := ofRawInt lapZero
      adjacency_eigenvalue_max := evMax
      laplacian_eigenvalue_max := lapMax
      singular_value_max      := svMax }

-- Witness: compute for a 2×2 matrix
#eval (computeSpectral 2 #[#[1, 1], #[0, 1]]).matrix_size.toInt  -- expect: 2
#eval (computeSpectral 2 #[#[1, 1], #[0, 1]]).rank.toInt          -- expect: 2
#eval (computeSpectral 2 #[#[1, 1], #[0, 1]]).trace_val.toInt     -- expect: 2

end SilverSight.PIST.SpectralN
