///|
/// 预处理引擎句柄
///
/// 每个后端构造一个 `Engine`,用函数闭包承载其行为。这是「可插拔契约」的载体:
/// 门面 `Compiler` 只依赖 `Engine` 的 `supports` / `compile`,不关心是哪个后端。
pub struct Engine {
  name_fn : () -> String
  supports_fn : (Format) -> Bool
  compile_fn : (String) -> String raise CompileError
  compile_imports_fn : (String, (String) -> String raise CompileError) -> String raise CompileError
}

///|
/// 构造一个引擎
///
/// 三个回调分别返回引擎名、是否负责某格式、以及把源码编译为 CSS。
pub fn Engine::new(
  name~ : () -> String,
  supports~ : (Format) -> Bool,
  compile~ : (String) -> String raise CompileError,
  compile_imports~ : (String, (String) -> String raise CompileError) -> String raise CompileError,
) -> Engine {
  {
    name_fn: name,
    supports_fn: supports,
    compile_fn: compile,
    compile_imports_fn: compile_imports,
  }
}

///|
/// 引擎名
pub fn Engine::engine_name(self : Engine) -> String {
  (self.name_fn)()
}

///|
/// 是否负责某格式
pub fn Engine::supports(self : Engine, fmt : Format) -> Bool {
  (self.supports_fn)(fmt)
}

///|
/// 把源码编译为 CSS
pub fn Engine::compile(
  self : Engine,
  source : String,
) -> String raise CompileError {
  (self.compile_fn)(source)
}

///|
/// 带 @import 内联地编译
pub fn Engine::compile_imports(
  self : Engine,
  source : String,
  read : (String) -> String raise CompileError,
) -> String raise CompileError {
  (self.compile_imports_fn)(source, read)
}

///|

///|
/// 统一的编译错误
///
/// 把各个后端引擎的错误统一成一种 `CompileError`,让调用方只需处理它。
pub(all) suberror CompileError {
  /// 没有任何引擎负责该格式
  NoEngine(Format)
  /// 引擎在编译过程中失败
  EngineFailed(engine~ : String, message~ : String)
  /// 语法尚未支持(如 sass 缩进语法)
  UnsupportedSyntax(String)
} derive(Debug)

///|
pub extend CompileError with @debug.Debug::{to_repr}

///|
pub extend CompileError with Show::{to_string, output}

///|
pub impl Show for CompileError with fn to_string(self) {
  match self {
    NoEngine(fmt) => "no engine for format \{fmt}"
    EngineFailed(engine~, message~) => "\{engine} failed: \{message}"
    UnsupportedSyntax(feature) => "unsupported syntax: \{feature}"
  }
}

///|

///|
/// 支持的 CSS 预处理格式
///
/// 分发给后端引擎的格式标签。
pub(all) enum Format {
  Scss
  Less
  Css
} derive(Debug, Eq)

///|
pub extend Format with @debug.Debug::{to_repr}

///|
pub extend Format with Eq::{equal, not_equal}

///|
pub extend Format with Show::{to_string, output}

///|
/// 启发式自动识别源码格式
///
/// 优先判 SCSS(含 `$` 变量或 Sass 控制指令),其次判 LESS
/// (`@name :` 变量定义),否则当作普通 CSS。
pub fn Format::detect(source : String) -> Format {
  if Format::looks_scss(source) {
    return Scss
  }
  if Format::looks_less(source) {
    return Less
  }
  Css
}

///|
fn Format::looks_scss(source : String) -> Bool {
  if source.contains("$") {
    return true
  }
  let controls = [
    "@mixin", "@include", "@each", "@for", "@extend", "@function", "@use", "@forward",
    "@while", "@at-root", "@if", "@else", "@return", "@debug", "@warn", "@error",
    "@content",
  ]
  for k in controls {
    if source.contains(k) {
      return true
    }
  }
  false
}

///|
/// 判断是否存在 LESS 变量定义(`@name : expr`)
fn Format::looks_less(source : String) -> Bool {
  let n = source.length()
  let mut i = 0
  while i < n {
    if source[i] == '@' {
      if is_less_variable_at(source, i) {
        return true
      }
    }
    i += 1
  }
  false
}

///|
/// 判断 `source[i]`(为 `@`)处是否为 LESS 变量定义
fn is_less_variable_at(source : String, i : Int) -> Bool {
  let n = source.length()
  let mut j = i + 1
  if j >= n || !is_ident_start(source[j]) {
    return false
  }
  while j < n && is_ident_char(source[j]) {
    j += 1
  }
  let mut k = j
  while k < n && source[k] == ' ' {
    k += 1
  }
  k < n && source[k] == ':'
}

///|
fn is_ident_start(u : UInt16) -> Bool {
  (u >= 'A' && u <= 'Z') || (u >= 'a' && u <= 'z') || u == '_' || u == '-'
}

///|
fn is_ident_char(u : UInt16) -> Bool {
  is_ident_start(u) || (u >= '0' && u <= '9')
}

///|
pub impl Show for Format with fn to_string(self) {
  match self {
    Scss => "scss"
    Less => "less"
    Css => "css"
  }
}

///|

///|
/// 编译入口的输入形态
///
/// 传给 `compile_input` 的输入,决定格式如何解析。
pub(all) enum Input {
  /// 源码正文,格式走自动识别
  Source(String)
  /// 源码正文 + 显式指定格式
  SourceWithFormat(String, Format)
  /// 文件路径(用注入 reader 读取)
  File(String)
} derive(Debug, Eq)

///|
pub extend Input with @debug.Debug::{to_repr}

///|
pub extend Input with Eq::{equal, not_equal}