// tag 名字表与敏感分类表。
//
// 表里每一个编号都逐条对着 exiftool 公布的 EXIF/GPS tag 名表
// (exiftool.org/TagNames/EXIF.html 与 GPS.html)核过,核对日期 2026-09-24。
// 没核过的一律不进表——宁可打印"0x927c",也不要打印一个错名字,
// 因为这个名字会被人拿去决定删什么。

///|
/// 条目所在的目录。同名 tag 在不同目录里是不同含义,所以查名字必须带目录。
pub(all) enum Dir {
  Ifd0
  Exif
  Gps
  Interop
} derive(Debug, Eq)

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

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

///|
pub fn Dir::label(self : Dir) -> String {
  match self {
    Ifd0 => "IFD0"
    Exif => "Exif"
    Gps => "GPS"
    Interop => "Interop"
  }
}

///|
pub impl Show for Dir with fn output(self : Dir, logger) {
  logger.write(self.label())
}

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

///|
/// tag 的规范名;表里没有就返回 `0xNNNN`。
pub fn tag_name(dir : Dir, tag : Int) -> String {
  let named = match dir {
    Ifd0 => ifd0_name(tag)
    Exif => exif_name(tag)
    Gps => gps_name(tag)
    Interop => interop_name(tag)
  }
  match named {
    Some(s) => s
    None => tag_hex(tag)
  }
}

///|
fn ifd0_name(tag : Int) -> String? {
  match tag {
    0x010f => Some("Make")
    0x0110 => Some("Model")
    0x0112 => Some("Orientation")
    0x0131 => Some("Software")
    0x0132 => Some("DateTime")
    0x013b => Some("Artist")
    0x0201 => Some("JPEGInterchangeFormat")
    0x0202 => Some("JPEGInterchangeFormatLength")
    0x02bc => Some("XMP")
    0x02bd => Some("PhotoshopSettings")
    0x8298 => Some("Copyright")
    0x83bb => Some("IptcNaa")
    0x8769 => Some("ExifOffset")
    0x8773 => Some("InterColorProfile")
    0x8825 => Some("GPSInfo")
    _ => None
  }
}

///|
fn exif_name(tag : Int) -> String? {
  match tag {
    0x8825 => Some("GPSInfo")
    0x9003 => Some("DateTimeOriginal")
    0x9004 => Some("DateTimeDigitized")
    0x9010 => Some("OffsetTime")
    0x9011 => Some("OffsetTimeOriginal")
    0x9012 => Some("OffsetTimeDigitized")
    0x927c => Some("MakerNote")
    0x9286 => Some("UserComment")
    0x9290 => Some("SubsecTime")
    0x9291 => Some("SubsecTimeOriginal")
    0xa005 => Some("InteropOffset")
    0xa420 => Some("ImageUniqueID")
    0xa431 => Some("BodySerialNumber")
    0xa435 => Some("LensSerialNumber")
    _ => None
  }
}

///|
/// Interoperability IFD 的名字表。
///
/// 只收真实语料里出现过的四个编号(对拍语料里带 Interop IFD 的 44 个文件,
/// 键集合恰好是 `0x1/0x2` 或 `0x1/0x2/0x1001/0x1002` 两种)。规范里还有别的编号,
/// 但没在这儿被任何相机写出过——把没验证过的名字写进表里,
/// 就等于把猜测冒充成这库的认识。
fn interop_name(tag : Int) -> String? {
  match tag {
    0x0001 => Some("InteropIndex")
    0x0002 => Some("InteropVersion")
    0x1001 => Some("RelatedImageWidth")
    0x1002 => Some("RelatedImageHeight")
    _ => None
  }
}

///|
fn gps_name(tag : Int) -> String? {
  match tag {
    0x0000 => Some("GPSVersionID")
    0x0001 => Some("GPSLatitudeRef")
    0x0002 => Some("GPSLatitude")
    0x0003 => Some("GPSLongitudeRef")
    0x0004 => Some("GPSLongitude")
    0x0005 => Some("GPSAltitudeRef")
    0x0006 => Some("GPSAltitude")
    0x0007 => Some("GPSTimeStamp")
    0x0008 => Some("GPSSatellites")
    0x0009 => Some("GPSStatus")
    0x000a => Some("GPSMeasureMode")
    0x000b => Some("GPSDOP")
    0x000c => Some("GPSSpeedRef")
    0x000d => Some("GPSSpeed")
    0x000e => Some("GPSTrackRef")
    0x000f => Some("GPSTrack")
    0x0010 => Some("GPSImgDirectionRef")
    0x0011 => Some("GPSImgDirection")
    0x0012 => Some("GPSMapDatum")
    0x0013 => Some("GPSDestLatitudeRef")
    0x0014 => Some("GPSDestLatitude")
    0x0015 => Some("GPSDestLongitudeRef")
    0x0016 => Some("GPSDestLongitude")
    0x0017 => Some("GPSDestBearingRef")
    0x0018 => Some("GPSDestBearing")
    0x0019 => Some("GPSDestDistanceRef")
    0x001a => Some("GPSDestDistance")
    0x001b => Some("GPSProcessingMethod")
    0x001c => Some("GPSAreaInformation")
    0x001d => Some("GPSDateStamp")
    0x001e => Some("GPSDifferential")
    0x001f => Some("GPSHPositioningError")
    _ => None
  }
}

///|
/// 敏感条目总表:(所在目录, tag 编号, 类别)。
///
/// 目录必须写进表里:同一个编号在 IFD0 和 Exif 下是两个不同的 tag,
/// 只按编号匹配会给出一条指错方向、名字打不出来的删除记录,
/// 而这份报告是拿来决定"删了什么"的。
///
/// 每一个编号都对着 exiftool 的 tag 名表核过(见文件头)。
let sensitive_tags : Array[(Dir, Int, Sensitive)] = [
  // 署名与内容指纹:直接指向人
  (Ifd0, 0x013b, Identity),
  (Exif, 0xa420, Identity),
  // 机身/镜头序列号:能把两张"匿名"照片关联到同一台机器
  (Exif, 0xa431, DeviceId),
  (Exif, 0xa435, DeviceId),
  // 厂商私有块:内容不受规范约束,坐标常藏在里面
  (Exif, 0x927c, MakerNote),
  // 整包载体:条目本身只有一句"这里另有一整套元数据",
  // 里面的字段归 XMP / Photoshop / IPTC 各自的规范管,我们逐条判断不了。
  (Ifd0, 0x02bc, Carrier),
  (Ifd0, 0x02bd, Carrier),
  (Ifd0, 0x83bb, Carrier),
  // `0x8773` (InterColorProfile) 刻意不在任何类别里:ICC 是一份色彩变换表,
  // 不是元数据的第二副本,里面既指不出人也指不出地点。把它当载体摘掉,
  // 换来的不是隐私而是产物偏色——摘别人的颜色不是脱敏工具的权利。
  // 用户写的话
  (Exif, 0x9286, Comments),
  // 能把照片排进时间线的一切
  (Ifd0, 0x0132, Timestamps),
  (Exif, 0x9003, Timestamps),
  (Exif, 0x9004, Timestamps),
  (Exif, 0x9010, Timestamps),
  (Exif, 0x9011, Timestamps),
  (Exif, 0x9012, Timestamps),
  (Exif, 0x9290, Timestamps),
  (Exif, 0x9291, Timestamps),
]

///|
/// 敏感信息的类别。
///
/// 刻意不做成 String:类别是策略的开关,"加了新类别却忘了在策略里处理"
/// 必须变成编译错误,不能变成"静默不删、数据照原样发出去"。
pub(all) enum Sensitive {
  /// GPS 目录里的全部条目,以及坐标本身
  Location
  /// 署名、内容指纹:直接指向人
  Identity
  /// 机身/镜头序列号:能把两台"匿名"照片关联到同一台机器
  DeviceId
  /// 厂商私有块:内容不受规范约束,坐标常藏在里面
  MakerNote
  /// 用户写的话
  Comments
  /// 能把照片排进时间线的一切
  Timestamps
  /// 整包的外部元数据载体:XMP / Photoshop IRB / IPTC-NAA。
  ///
  /// 前六类说的是"这一条是什么",这一类说的是"这里还有一整套元数据":
  /// 载体里的字段编号、命名、编码都归另一份规范管,按条判断力所不及,
  /// 只能整包丢。所以这一类不是理论负担——那批外来裸 TIFF(104 份)按 TIFF 6.0
  /// 走一遍 IFD0,带 XMP(`0x02bc`)的 10 份、带 IPTC-NAA(`0x83bb`)的 1 份、
  /// 带 Photoshop(`0x02bd`)的 0 份。这 10 份里有 1 份(`ifd_tag_type.tiff`)
  /// 在 Exif 指针上用了类型 13,读取侧按设计先拒绝、走不到那条条目,所以这个数
  /// 只回答"条目在不在",不回答"库读不读得通"(对拍闸按字节里的包标记数是 9 份,
  /// 差的正是这一份)。JPEG 那一侧的 IPTC 不住在 IFD0 里,走 APP13 段,
  /// 由容器层整段摘(见 `moonmeta_jpeg.mbt`):99 张真实照片那批里带包 10 份,
  /// 摘除并披露 10 份。
  Carrier
} derive(Debug, Eq)

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

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

///|
/// 用于展示的短名。
pub fn Sensitive::label(self : Sensitive) -> String {
  match self {
    Location => "location"
    Identity => "identity"
    DeviceId => "device_id"
    MakerNote => "maker_note"
    Comments => "comments"
    Timestamps => "timestamps"
    Carrier => "carrier"
  }
}

///|
pub impl Show for Sensitive with fn output(self : Sensitive, logger) {
  logger.write(self.label())
}

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

///|
/// 这个条目属于哪一类敏感信息;`None` 表示不属于任何被策略覆盖的类别。
///
/// GPS 目录整体算 `Location`:目录里的 tag 没有一个是无辜的,
/// 连 `GPSVersionID` 都在告诉别人"这张图带过坐标"。
///
/// `Copyright` 不在任何类别里——它是权利声明,抹掉它本身就是一种伤害。
pub fn sensitive_category(dir : Dir, tag : Int) -> Sensitive? {
  if dir == Gps {
    Some(Location)
  } else {
    for e in sensitive_tags {
      let (d, t, cat) = e
      if d == dir && t == tag {
        return Some(cat)
      }
    }
    None
  }
}