///|
/// 编译门面:持有若干可插拔引擎
///
/// 门面只负责「识别格式 → 路由到引擎 → 统一错误」,不绑定任何具体预处理引擎。
pub struct Compiler {
  engines : Array[Engine]
}

///|
/// 用一组引擎构造门面
pub fn Compiler::new(engines : Array[Engine]) -> Compiler {
  { engines, }
}

///|
/// 自动识别格式并编译
pub fn Compiler::compile(
  self : Compiler,
  source : String,
) -> String raise CompileError {
  let fmt = Format::detect(source)
  self.compile_with_format(source, fmt)
}

///|
/// 以显式格式编译
pub fn Compiler::compile_with_format(
  self : Compiler,
  source : String,
  fmt : Format,
) -> String raise CompileError {
  let engine = self.pick(fmt)
  engine.compile(source)
}

///|
/// 依据输入形态编译
pub fn Compiler::compile_input(
  self : Compiler,
  input : Input,
) -> String raise CompileError {
  match input {
    Source(source) => self.compile(source)
    SourceWithFormat(source, fmt) => self.compile_with_format(source, fmt)
    File(_) =>
      raise CompileError::EngineFailed(
        engine="compile_input",
        message="File input requires compile_many with a reader",
      )
  }
}

///|
/// 编译多个输入(文件/字符串可混用),逐个编译并拼接
///
/// 每个输入先归一化成源码(`File` 用 `read` 读取,`Source` 直接用),
/// 再交给 SCSS 引擎做 `@import` 内联编译。
pub fn Compiler::compile_many(
  self : Compiler,
  inputs : Array[Input],
  read : (String) -> String raise CompileError,
) -> String raise CompileError {
  let scss = self.pick(Scss)
  let parts : Array[String] = []
  for input in inputs {
    let src = match input {
      Source(s) => s
      SourceWithFormat(s, _) => s
      File(p) => read(p)
    }
    parts.push(scss.compile_imports(src, read))
  }
  parts.join("\n")
}

///|
/// 编译文件
///
/// 读取函数由调用方注入,核心不耦合具体 IO 后端,跨 target 可用。
pub fn Compiler::compile_file(
  self : Compiler,
  path : String,
  read : (String) -> String raise CompileError,
) -> String raise CompileError {
  let source = read(path)
  self.compile(source)
}

///|
/// 按格式挑选引擎
fn Compiler::pick(self : Compiler, fmt : Format) -> Engine raise CompileError {
  for e in self.engines {
    if e.supports(fmt) {
      return e
    }
  }
  raise CompileError::NoEngine(fmt)
}