function export_phy_to_json(phy, jsonFile, cfg)

%EXPORT_PHY_TO_JSON  由 phy + case_config 生成 OpticsFEM 散射 JSON（通用）。

% BoundaryFlag(i) = 网格面域 DomainOfTri 编号 i 的边界类型（0 PMC, 2 SBC, 4 PBC）。



if nargin < 1 || isempty(phy)

  if ~exist('case_config', 'file')

    error('未传入 phy，且当前目录无 case_config.m');

  end

  [phy, cfg] = case_config();

  if nargin < 2 || isempty(jsonFile)

    jsonFile = fullfile(pwd, cfg.jsonFile);

  end

elseif nargin < 3 || isempty(cfg)

  if exist('case_config', 'file')

    [~, cfg] = case_config();

  else

    cfg = struct();

  end

  cfg = fill_cfg_defaults(cfg, phy);

  if nargin < 2 || isempty(jsonFile)

    if isfield(cfg, 'jsonFile')

      jsonFile = fullfile(pwd, cfg.jsonFile);

    else

      jsonFile = fullfile(pwd, 'sbc3d.json');

    end

  end

else

  cfg = fill_cfg_defaults(cfg, phy);

end



outFaces = phy.out(:)';

incFaces = phy.inc(:)';

sbcIndex = unique([outFaces, incFaces], 'stable');

nSbc = numel(sbcIndex);

sbcType = zeros(1, nSbc);

for k = 1:nSbc

  if ismember(sbcIndex(k), incFaces)

    sbcType(k) = 1;

  else

    sbcType(k) = 0;

  end

end



[nbrBoundary, boundaryFlag] = infer_boundary_flag(phy, cfg);



nDom = numel(phy.eps);

doc = struct();

doc.FemType = cfg.FemType;

doc.EletricType = cfg.EletricType;

doc.lambda = phy.lda0;

doc.NbrBoundary = nbrBoundary;

doc.BoundaryFlag = boundaryFlag;



if isfield(phy, 'src') && isfield(phy, 'dst') && ~isempty(phy.src)

  if isfield(cfg, 'pbcPhi')

    pbcPhi = cfg.pbcPhi;

  else

    pbcPhi = exp(1i * 2 * pi / 6);

  end

  if isfield(cfg, 'pbcAngle')

    pbcAngle = cfg.pbcAngle;

  else

    pbcAngle = pi / 3;

  end

  doc.pbc = struct( ...

    'srcIndex', phy.src(:)', ...

    'dstIndex', phy.dst(:)', ...

    'phiR', real(pbcPhi(:)'), ...

    'phiI', imag(pbcPhi(:)'), ...

    'pbcAngle', pbcAngle);

end



doc.sbc = struct( ...

  'Index', sbcIndex, ...

  'SBCType', sbcType, ...

  'E0x', {repmat({num2str(phy.Einc(1))}, 1, nSbc)}, ...

  'E0y', {repmat({num2str(phy.Einc(2))}, 1, nSbc)}, ...

  'E0z', {repmat({num2str(phy.Einc(3))}, 1, nSbc)}, ...

  'kx', zeros(1, nSbc), ...

  'ky', zeros(1, nSbc), ...

  'kz', zeros(1, nSbc));

doc.NbrDomain = nDom;

doc.domainType = repmat(2, 1, nDom);

doc.domainIndex = 0:(nDom - 1);

doc.matType = zeros(1, nDom);

doc.epsilonrR = phy.eps(:)';

doc.epsilonrI = zeros(1, nDom);

doc.murR = phy.mur(:)';

doc.murI = zeros(1, nDom);

doc.chiheR = zeros(1, nDom);

doc.chiehR = zeros(1, nDom);

doc.chiheI = zeros(1, nDom);

doc.chiehI = zeros(1, nDom);

doc.sigma = zeros(1, nDom);

doc.n = ones(1, nDom);

doc.k = zeros(1, nDom);

doc.MeshFile = cfg.MeshFile;

doc.OutFile = cfg.OutFile;



txt = pretty_json(doc);

fid = fopen(jsonFile, 'w');

if fid < 0

  error('无法写入: %s', jsonFile);

end

cleanup = onCleanup(@() fclose(fid));

fprintf(fid, '%s', txt);

fprintf('已写出 %s (%d 字节, lambda=%.4g, NbrBoundary=%d)\n', ...

  jsonFile, length(txt), phy.lda0, doc.NbrBoundary);

end



function cfg = fill_cfg_defaults(cfg, phy)

if nargin < 2, phy = struct(); end

if ~isfield(cfg, 'FemType'), cfg.FemType = 4; end

if ~isfield(cfg, 'EletricType'), cfg.EletricType = 2; end

if ~isfield(cfg, 'MeshFile'), cfg.MeshFile = 'SBCmesh.dat'; end

if ~isfield(cfg, 'OutFile'), cfg.OutFile = './OutFile'; end

if ~isfield(cfg, 'NbrBoundary') || ~isfield(cfg, 'BoundaryFlag')

  [cfg.NbrBoundary, cfg.BoundaryFlag] = infer_boundary_flag(phy, cfg);

end

end



function [nbrBoundary, boundaryFlag] = infer_boundary_flag(phy, cfg)

% Prefer explicit cfg; else build from phy face domains (PBC case 1..9).

if isfield(cfg, 'NbrBoundary') && isfield(cfg, 'BoundaryFlag') ...

    && numel(cfg.BoundaryFlag) == cfg.NbrBoundary

  nbrBoundary = cfg.NbrBoundary;

  boundaryFlag = cfg.BoundaryFlag;

  return;

end



if isfield(phy, 'src') && isfield(phy, 'dst') && ~isempty(phy.src)

  allDom = [phy.src(:); phy.dst(:); phy.out(:); phy.inc(:)];

  nbrBoundary = max(allDom);

  boundaryFlag = zeros(1, nbrBoundary);

  for d = 1:nbrBoundary

    if ismember(d, [phy.src(:); phy.dst(:)])

      boundaryFlag(d) = 4;

    elseif ismember(d, [phy.out(:); phy.inc(:)])

      boundaryFlag(d) = 2;

    else

      boundaryFlag(d) = 0;

    end

  end

  return;

end



% Legacy SBC full model (14 COMSOL boundaries)

nbrBoundary = 14;

boundaryFlag = [0, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 2];

end



function txt = pretty_json(doc)

lines = cell(1, 50);

n = 0;

n = n + 1; lines{n} = '{';

n = n + 1; lines{n} = sprintf('  "FemType": %d,', doc.FemType);

n = n + 1; lines{n} = sprintf('  "EletricType": %d,', doc.EletricType);

n = n + 1; lines{n} = sprintf('  "lambda": %s,', json_num(doc.lambda));

n = n + 1; lines{n} = sprintf('  "NbrBoundary": %d,', doc.NbrBoundary);

n = n + 1; lines{n} = sprintf('  "BoundaryFlag": %s,', json_int_array(doc.BoundaryFlag));



if isfield(doc, 'pbc')

  n = n + 1; lines{n} = '  "pbc": {';

  n = n + 1; lines{n} = sprintf('    "srcIndex": %s,', json_int_array(doc.pbc.srcIndex));

  n = n + 1; lines{n} = sprintf('    "dstIndex": %s,', json_int_array(doc.pbc.dstIndex));

  n = n + 1; lines{n} = sprintf('    "phiR": %s,', json_float_array(doc.pbc.phiR));

  n = n + 1; lines{n} = sprintf('    "phiI": %s,', json_float_array(doc.pbc.phiI));

  n = n + 1; lines{n} = sprintf('    "pbcAngle": %s', json_num(doc.pbc.pbcAngle));

  n = n + 1; lines{n} = '  },';

end



n = n + 1; lines{n} = '  "sbc": {';

n = n + 1; lines{n} = sprintf('    "Index": %s,', json_int_array(doc.sbc.Index));

n = n + 1; lines{n} = sprintf('    "SBCType": %s,', json_int_array(doc.sbc.SBCType));

n = n + 1; lines{n} = sprintf('    "E0x": %s,', json_str_array(doc.sbc.E0x));

n = n + 1; lines{n} = sprintf('    "E0y": %s,', json_str_array(doc.sbc.E0y));

n = n + 1; lines{n} = sprintf('    "E0z": %s,', json_str_array(doc.sbc.E0z));

n = n + 1; lines{n} = sprintf('    "kx": %s,', json_int_array(doc.sbc.kx));

n = n + 1; lines{n} = sprintf('    "ky": %s,', json_int_array(doc.sbc.ky));

n = n + 1; lines{n} = sprintf('    "kz": %s', json_int_array(doc.sbc.kz));

n = n + 1; lines{n} = '  },';

n = n + 1; lines{n} = sprintf('  "NbrDomain": %d,', doc.NbrDomain);

n = n + 1; lines{n} = sprintf('  "domainType": %s,', json_int_array(doc.domainType));

n = n + 1; lines{n} = sprintf('  "domainIndex": %s,', json_int_array(doc.domainIndex));

n = n + 1; lines{n} = sprintf('  "matType": %s,', json_int_array(doc.matType));

n = n + 1; lines{n} = sprintf('  "epsilonrR": %s,', json_float_array(doc.epsilonrR));

n = n + 1; lines{n} = sprintf('  "epsilonrI": %s,', json_float_array(doc.epsilonrI));

n = n + 1; lines{n} = sprintf('  "murR": %s,', json_float_array(doc.murR));

n = n + 1; lines{n} = sprintf('  "murI": %s,', json_float_array(doc.murI));

n = n + 1; lines{n} = sprintf('  "chiheR": %s,', json_float_array(doc.chiheR));

n = n + 1; lines{n} = sprintf('  "chiehR": %s,', json_float_array(doc.chiehR));

n = n + 1; lines{n} = sprintf('  "chiheI": %s,', json_float_array(doc.chiheI));

n = n + 1; lines{n} = sprintf('  "chiehI": %s,', json_float_array(doc.chiehI));

n = n + 1; lines{n} = sprintf('  "sigma": %s,', json_float_array(doc.sigma));

n = n + 1; lines{n} = sprintf('  "n": %s,', json_float_array(doc.n));

n = n + 1; lines{n} = sprintf('  "k": %s,', json_float_array(doc.k));

n = n + 1; lines{n} = sprintf('  "MeshFile": "%s",', doc.MeshFile);

n = n + 1; lines{n} = sprintf('  "OutFile": "%s"', doc.OutFile);

n = n + 1; lines{n} = '}';

lines = lines(1:n);

txt = join_lines(lines);

end



function s = join_lines(lines)

if isempty(lines)

  s = '';

  return;

end

s = lines{1};

for i = 2:numel(lines)

  s = [s, sprintf('\n'), lines{i}]; %#ok<AGROW>

end

end



function s = json_num(x)

s = num2str(x, '%.16g');

end



function s = json_int_array(v)

v = v(:)';

parts = cell(1, numel(v));

for i = 1:numel(v)

  parts{i} = sprintf('%d', round(v(i)));

end

s = sprintf('[%s]', join_csv(parts));

end



function s = json_float_array(v)

v = v(:)';

parts = cell(1, numel(v));

for i = 1:numel(v)

  parts{i} = num2str(v(i), '%.16g');

end

s = sprintf('[%s]', join_csv(parts));

end



function s = json_str_array(c)

parts = cell(1, numel(c));

for i = 1:numel(c)

  parts{i} = sprintf('"%s"', char(c{i}));

end

s = sprintf('[%s]', join_csv(parts));

end



function s = join_csv(parts)

if isempty(parts)

  s = '';

elseif numel(parts) == 1

  s = parts{1};

else

  s = parts{1};

  for i = 2:numel(parts)

    s = [s, ', ', parts{i}]; %#ok<AGROW>

  end

end

end

