import os
import sys
import argparse
from types import SimpleNamespace
import re
import numpy as np
import pandas as pd
from jinja2 import Template
from jinja2 import Environment, FileSystemLoader, select_autoescape
from bs4 import BeautifulSoup
#from collections.abc import Sequence

# 必要なライブラリのインポート
try:
    import docx
except ImportError:
    docx = None
try:
    import openpyxl
except ImportError:
    openpyxl = None
import csv
import io
from pathlib import Path


tkProg_Root = os.environ.get('tkProg_Root', None)
tkprog_X_path = os.environ.get('tkprog_X_path', None)
tklib_path = os.path.join(tkProg_Root, 'tklib', 'python')
sys.path.append(tklib_path)

try:
    import tklib.tkimport as imp
except Exception as e:
    print()
    print("######################################################################")
    print("###########  ERROR ERROR ERROR ERROR ERROR ERROR #####################")
    print("######################################################################")
    print(f"# Failed to import [tklib.tkimport] module ({e}).")
    print(f"#  Add [tkProg]{os.sep}tklib{os.sep}python to PYTHONPATH variable")
    print(f"#  Current PYTHONPATH:", sys.path)
    print("######################################################################")
    input("Press ENTER to terminate>>\n")
    exit()

from tklib.tkcrystal.tkvasp import tkVASP
from read_inf import read_inf_all


DEFAULT_BASE_DIR = ""
DEFAULT_TEMPLATE_XLSM_FILE = "template-template.xlsm"
DEFAULT_TEMPLATE_HTML_VSTACK_FILE = "template-template_vstack.html"

image_exts = ['.png', '.ico', '.jpeg', '.jpg', '.bmp']
text_exts  = ['.txt', '.md']
html_exts  = ['.html', '.htm']
csv_exts   = ['.csv']
excel_exts = ['.xlsx', '.xlsm']
word_exts  = [".docx"]
pdf_exts   = [".pdf"]

def is_array_like(val):
    """
    配列に類似したオブジェクトかどうかを判定。
    list, tuple, numpy.ndarray, pandas.Series, pandas.DataFrameに対応。
    """
    return (
        isinstance(val, list) or 
        isinstance(val, tuple) or 
        isinstance(val, np.ndarray) or 
        isinstance(val, pd.Series) or 
        isinstance(val, pd.DataFrame)
    )

def initialize():
    parser = argparse.ArgumentParser(description="Clipboard to speech tool")
    parser.add_argument(
        "--mode",
        choices=["list", "make", "make_template", ""],
        default="list",
        help="mode"
    )
    parser.add_argument(
        "--base_dir",
        default = DEFAULT_BASE_DIR,
        help="Base directory"
    )
    parser.add_argument(
        "--subdirs",
        default = "",
        help="Sub directories to search"
    )
    parser.add_argument(
        "--prefix",
        default = "",
        help="Prefix for keys of replace_dict"
    )
    parser.add_argument(
        "--template_file",
        default="template.txt",
        help="Template file (.txt/.md/.html/.xlsx/.xlsm/.docx(/.csv)"
    )
    parser.add_argument(
        "--root_template_file",
        default="",
        help="Template file for template (.txt/.md/.html/.xlsx/.xlsm/.docx(/.csv)"
    )
    parser.add_argument(
        "--output_file",
        default="output.txt",
        help="Output file (extenstion will be changed according to template_file"
    )
    parser.add_argument(
        "--append",
        action="store_true",
        help="Append to the output file instead of overwriting"
    )
    parser.add_argument(
        "--layout",
        default = '', 
        choices=["", "vstack", "hstack", "page", "table"],
        help="Layout of template"
    )
    parser.add_argument(
        "--max_keys",
        type = int, default = -1, 
        help="For debug: Max number of keys to be rendered"
    )

    return parser

def render_template(template, replace_dict):
    result = template.render(replace_dict, zip = zip)
#        try:
#            result = template.render(replace_dict)
#        except Exception as e:
#            print(f"Error in replace_template(): {type(e).__name__}: {e}")
#            exit()
    return result

def replace_template(template_content, replace_dict, force_replace = False, use_jinja2 = True):
    """
    テンプレート文字列内の {{key}} を辞書の値で置換する関数。
    存在しないキーは空白に置換します。
    """

# For Jinja2
    if use_jinja2:
        template = Template(template_content)
        return render_template(template, replace_dict)

# Simple replace by regex
    keys_in_template = re.findall(r"\{\{\s*(.*?)\s*\}\}", template_content)
    for key in set(keys_in_template):
        if key in replace_dict:
            value = str(replace_dict[key])
        else:
            if force_replace:
                value = ""
            else:
                value = None

        if value is not None:
            pattern = r"\{\{\s*" + re.escape(key) + r"\s*\}\}"
            template_content = re.sub(pattern, value, template_content)
            
    return template_content

def show_list(replace_dict):
    for key, val in replace_dict.items():
        if is_array_like(val):
            print(f"{key}: {type(val).__name__} (length: {len(val)})")
        else:
            print(f"{key}: {val}")

def html_a(url, text):
    return f'<a href="{url}">{text}</a>'

def html_iframe(url):
    return f'<iframe src="{url}" width="90%" height="400">{url}</iframe>'

def html_iframe_pdf(url):
    return f'<iframe src="{url}" width="600" height="400" style="border: none;"></iframe>'

def html_object_pdf(url, alt_text):
    return f'''<object data="{url}" width="600" height="400" type="application/pdf">
{alt_text}<a href="{url}">Download here</a>
</object>'''

def html_img(url):
    return f'<img src="{url}" width="300" style="display: inline;">'
    
def get_file_tag_html(global_key, key2):
    place_holder = "{{ " + key2 + " }}"
    if 'ImageFile' in global_key:
        str = html_a(place_holder, place_holder) + ' ' + html_img(place_holder)
    elif 'HTMLFile' in global_key:
        str = html_a(place_holder, place_holder) + ' ' + html_iframe(place_holder)
    elif 'TextFile' in global_key:
        str = html_a(place_holder, place_holder) + ' ' + html_iframe(place_holder)
    elif 'PDFFile' in global_key:
        str = html_a(place_holder, place_holder) + ' ' + html_iframe_pdf(place_holder)
    else:
        str = "{{ " + key2 + " }}"
    return str

def get_file_tag_xlsx(global_key, key2):
    place_holder = "{{ " + key2 + " }}"
    return place_holder

def get_file_tag_text(global_key, key2):
    place_holder = "{{ " + key2 + " }}"
    if 'ImageFile' in global_key:
        str = f"![{place_holder}]({place_holder})"
    elif 'HTMLFile' in global_key:
        str = f"[{place_holder}]({place_holder})"
    elif 'TextFile' in global_key:
        str = f"[{place_holder}]({place_holder})"
    elif 'PDFFile' in global_key:
        str = f"[{place_holder}]({place_holder})"
    else:
        str = place_holder
    return str

def get_file_tag(global_key, key2, type):
    if type == 'html':
        return get_file_tag_html(global_key, key2)
    if type == 'text':
        return get_file_tag_text(global_key, key2)
    if type == 'xlsx':
        return get_file_tag_xlsx(global_key, key2)
    if type == 'csv':
        return get_file_tag_xlsx(global_key, key2)

    return "{{ " + key2 + " }}"

def get_place_holder_html(key, val, end = "<br>\n"):
    if type(val) is dict:
        str = f"{key}: "
        for key2, val2 in val.items():
            str += get_file_tag(key, f"item.{key2}", type = 'html')
        return str + end

    if is_array_like(val):
        if len(val) > 0 and type(val[0]) is dict:
            str = f"{key}:" \
                 + f"{{% for item in {key} %}}\n"
            for key2, val2 in val[0].items():
                str += get_file_tag(key, f"item.{key2}", type = 'html') + " "
            str += f"{{% endfor %}}" + end
            return str
        else:
            str = f"{key}:" \
                + f"{{% for item in {key} %}}\n"
            str += get_file_tag(key, "item", type = 'html')
            return str + f"{{% endfor %}}" + end

    return f"{key}: {{{{ {key} }}}}" + end


#markdown
#[PDFファイルをダウンロード](example.pdf)
#![代替テキスト](画像のURLまたはパス)
def get_place_holder_text(key, val):
    if type(val) is dict:
        str = f"{key}: "
        for key2, val2 in val.items():
            str += get_file_tag(key, f"item.{key2}", type = 'text')
        return str + "\n"

    if is_array_like(val):
        if len(val) > 0 and type(val[0]) is dict:
            str = f"{key}: {{% for item in {key} %}}\n"
            for key2, val2 in val[0].items():
                str += get_file_tag(key, f"item.{key2}", type = 'text') + " "
            str += f"{{% endfor %}}\n"
            return str
        else:
            str = f"{key}:" \
                + f"{{% for item in {key} %}}\n"
            str += get_file_tag(key, "item", type = 'text')
            return str + f"{{% endfor %}}\n"

    return f"{key}: {{{{ {key} }}}}\n"

def get_place_holder_xlsx(key, val):
    if type(val) is dict:
        str = ""
        for key2, val2 in val.items():
            str += get_file_tag(key, f"item.{key2}", type = 'xlsx') + ","
        return str.rstrip(',')

    if is_array_like(val):
        if len(val) > 0 and type(val[0]) is dict:
            str = f"{{% for item in {key} %}}"
            for key2, val2 in val[0].items():
                str += get_file_tag(key, f"item.{key2}", type = 'xlsx') + '.'
            str += f"{{% endfor %}}"
            return str.rstrip(',')
        else:
            return f"{{% for item in {key} %}}" \
                 + get_file_tag(key, "item", type = 'xlsx') + '{% if not loop.last %},{% endif %}'\
                 + f"{{% endfor %}}"

    return "{{ " + key + " }}"

def get_place_holder_csv(key, val):
    return get_place_holder_xlsx(key, val)

def get_place_holder(key, val, type = 'text', end = "<br>\n"):
    if type == 'text':
        return get_place_holder_text(key, val)
    if type == 'html':
        return get_place_holder_html(key, val, end = end)
    if type == 'xlsx':
        return get_place_holder_xlsx(key, val)
    if type == 'csv':
        return get_place_holder_csv(key, val)
    return None

def make_template_txt(template_path, replace_dict):
    template_lines = []
    for key, val in replace_dict.items():
        place_holder = get_place_holder(key, val, type = 'text')
        template_lines.append(place_holder)

    with open(template_path, 'w', encoding='utf-8') as f:
        f.write("\n".join(template_lines)) 

    print(f"Template saved to [{template_path}]")

def make_template_html_hstack(template_path, root_template_path, replace_dict):
    """
    指定された辞書に基づいて、スタイル指定のないHTMLテーブル形式のJinja2テンプレートを生成します。

    Args:
        template_path (str): 生成するテンプレートファイルのパス。
        replace_dict (dict): テンプレートに含めるキーと、その値の例を含む辞書。
                             値がリストまたはタプルの場合、配列ライクとして扱われます。
    """

    table_rows = []
    for key, val in replace_dict.items():
        row_content = f"<tr>\n  <td>{key}</td>\n  <td>"
        row_content += get_place_holder(key, val, type = 'html')
        row_content += "  </td>\n</tr>"
        table_rows.append(row_content)

    # 完全なHTMLテンプレートを構築
    html_template_content = f"""<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Generated Plain Template</title>
</head>

<body>
<h1>HTML template</h1>
<table border="1">
  <thead>
  <tr>
    <th>Key</th>
    <th>Placeholder</th>
  </tr>
  </thead>

  <tbody>
{os.linesep.join(table_rows)}
  </tbody>
</table>

</body>
</html>"""

    with open(template_path, 'w', encoding='utf-8') as f:
        f.write(html_template_content)

    print(f"テンプレートが [{template_path}] に保存されました。")

def get_html_table_head():
    head = f"""<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Generated Horizontal Template</title>
    <style>
        table {{
            width: 80%;
            border-collapse: collapse;
            margin: 20px auto;
        }}
        th, td {{
            border: 1px solid #ddd;
            padding: 8px;
            text-align: left;
        }}
        th {{
            background-color: #f2f2f2;
        }}
    </style>
</head>

<body>
<h1>HTML Horizontal Template</h1>
<table border="1">
"""
    return head
        
def get_html_table_tail():
    tail = """
</table>

</body>
</html>
"""
    return tail

def get_template_inf(root_template_path = ""):
    inf = {}

    if root_template_path and os.path.isfile(root_template_path):
        try:
            #tempmlate_pathからhtmlを読み込む
            with open(root_template_path, 'r', encoding='utf-8') as f:
                template_content = f.read()
        except:
            print(f"\nWarning in get_template_inf(): Could not read [{root_template_path}]\nUse default.\n")
            template_content = ""

        #template_contentの先頭から r"<\s*table\s+?>"までをinf["head"]に、r"<\s*/table.*?>"から最後までをinf["tail"]に代入
        if template_content:
            open_pat = re.compile(r"<\s*table\b.*?>", re.IGNORECASE | re.DOTALL)
            m_open = open_pat.search(template_content)
            if m_open:
                inf["head"] = template_content[:m_open.end()]
                print("inf[head]=",inf["head"])
            else:
                print(f"\nWarning in get_template_inf(): Could not find <table> tag\nUse default.\n")
            close_pat = re.compile(r"<\s*/table\b.*?>", re.IGNORECASE | re.DOTALL)
            m_close = close_pat.search(template_content)
            if m_close:
                inf["tail"] = template_content[m_close.start():]
            else:
                print(f"\nWarning in get_template_inf(): Could not find </table> tag\nUse default.\n")

    if inf.get("head", None) is None or inf.get("tail", None) is None:
        inf["head"] = get_html_table_head()
        inf["tail"] = get_html_table_tail()

    return inf

def make_template_html_vstack(template_path, root_template_path, replace_dict, max_keys = -1):
    """
    指定された辞書に基づいて、スタイル指定のないHTMLテーブル形式のJinja2テンプレートを生成します。
    キーを1行目（ヘッダー）、プレースホルダーを2行目（データ）に配置します。

    Args:
        template_path (str): 生成するテンプレートファイルのパス。
        replace_dict (dict): テンプレートに含めるキーと、その値の例を含む辞書。
                              値がリストまたはタプルの場合、配列ライクとして扱われます。
    """

    if root_template_path != "":
        js_type = 'vstack'
    else:
        js_type = None

    template_inf = get_template_inf(root_template_path)
    html_table_head = template_inf["head"]
    html_table_tail = template_inf["tail"]
#    print("html_table_head=", html_table_head)
#    print("html_table_tail=", html_table_tail)

    # ヘッダー行 (キーを<th>として)
    header_ths = []
    for idx, (key, val) in enumerate(replace_dict.items()):
        if max_keys >= 0 and idx >= max_keys: break

        if js_type == 'vstack':
            t = type(val)
            if t is float or t is int:
                placeholder = "例: >=80,<70, 30<x<80"
            else:
                placeholder = f"fileter herer"

            th_tag = f'    <th onclick="handleHeaderClick(event, {idx+1})">{key}<br>'\
                   + f'<input type="text" class="filter-input" '\
                   + f'oninput="filterColumn({idx+1}, this.value)" '\
                   + f'placeholder="{placeholder}"></th>'
        else:
            th_tag = f"    <th>{key}</th>"
        header_ths.append(th_tag)
    header_row_content = "<tr>\n" + "\n".join(header_ths) + "\n</tr>"

    # データ行 (プレースホルダーを<td>として)
    data_tds = []
    for idx, (key, val) in enumerate(replace_dict.items()):
        if max_keys >= 0 and idx >= max_keys: break

        td_content = "    <td>"
        td_content += get_place_holder(key, val, type = 'html', end = "")
        td_content += "</td>"
        data_tds.append(td_content)
    data_row_content = "<tr>\n" + "\n".join(data_tds) + "\n  </tr>"

    # 完全なHTMLテンプレートを構築
    table_content = f"""
<thead>
  {header_row_content}
</thead>

<tbody>
  {data_row_content}
</tbody>
"""
    html_template_content = html_table_head + table_content + html_table_tail

    with open(template_path, 'w', encoding='utf-8') as f:
        f.write(html_template_content)

    print(f"テンプレートが [{template_path}] に保存されました。")

def make_template_html_page(template_path, root_template_path, replace_dict):
    """
    指定された辞書に基づいて、スタイル指定のないHTMLテーブル形式のJinja2テンプレートを生成します。
    キーをとプレースホルダーの組み合わせを１行ずつ出力します
    Args:
        template_path (str): 生成するテンプレートファイルのパス。
        replace_dict (dict): テンプレートに含めるキーと、その値の例を含む辞書。
                              値がリストまたはタプルの場合、配列ライクとして扱われます。
    """

    body = ""
    for key, val in replace_dict.items():
        body += get_place_holder(key, val, type = 'html')

    html_template_content = f"""<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Generated Horizontal Template</title>
</head>
<body>
<h1>HTML Page Layout Template</h1>
{body}

</body>
</html>
"""

    with open(template_path, 'w', encoding='utf-8') as f:
        f.write(html_template_content)

    print(f"テンプレートが [{template_path}] に保存されました。")

def make_template_html(template_path, root_template_path, replace_dict, layout = '', max_keys = -1):
    if layout == "vstack":
        return make_template_html_vstack(template_path, root_template_path, replace_dict, max_keys = max_keys)
    if layout == "page":
        return make_template_html_page(template_path, root_template_path, replace_dict)
    else:
        return make_template_html_hstack(template_path, root_template_path, replace_dict)

def make_template_csv(template_path, replace_dict):
    try:
        with open(template_path, 'w', newline='', encoding='utf-8') as f:
            writer = csv.writer(f)
            
            header = list(replace_dict.keys())
            writer.writerow(header)
            
            template_row = []
            for key, val in replace_dict.items():
                teemplate_row.append(get_place_holder(key, val, type = 'csv'))
            writer.writerow(template_row)

        print(f"Template saved to [{template_path}]")
    except Exception as e:
        print(f"Error creating CSV template: {e}")
        return False
    return True


def make_template_xlsx(template_path, root_template_path, replace_dict):
    if openpyxl is None:
        print("openpyxl is not installed. Please install it with 'pip install openpyxl'.")
        return False

    if root_template_path is not None and root_template_path != "":
        wb = openpyxl.load_workbook(root_template_path, keep_vba = True)
        ws = wb.active
        ws.delete_rows(1, ws.max_row)
        ws.delete_cols(1, ws.max_column)
    else:
        wb = openpyxl.Workbook()
        ws = wb.active
        
    header = list(replace_dict.keys())
    ws.append(header)
        
    template_row = []
    for key, val in replace_dict.items():
        template_row.append(get_place_holder(key, val, type = 'xlsx'))
    ws.append(template_row)

    try:
        wb.save(template_path)
        print(f"Template saved to [{template_path}]")
    except Exception as e:
        print(f"Error in make_template_xlsx(): Failed to create template file [{template_path}]: {e}")
        return False
    return True


def make_template_docx(template_path, replace_dict):
    if docx is None:
        print("python-docx is not installed. Please install it with 'pip install python-docx'.")
        return False
        
    try:
        doc = docx.Document()
        
        template_lines = []
        for key, val in replace_dict.items():
            if is_array_like(val):
                template_lines.append("{}: {{% for item in {} %}}\n- {{{{ item }}}}\n{{% endfor %}}".format(key, key))
            else:
                template_lines.append(f"{key}: {{{{ {key} }}}}")
        
        for line in template_lines:
            doc.add_paragraph(line)
        
        doc.save(template_path)
        print(f"Template saved to [{template_path}]")
    except Exception as e:
        print(f"Error creating DOCX template: {e}")
        return False
    return True

def make_template(template_path, root_template_path, replace_dict, layout = "", confirm_overwrite = True, max_keys = -1):
    if confirm_overwrite and os.path.exists(template_path):
        print()
        print("###############################################")
        print(f"Template file [{template_path}] already exists. Overwrite? (y/n)")
        if input().strip().lower() != 'y':
            print("Aborted.")
            return False

    template_ext = os.path.splitext(template_path)[1]
    if template_ext in text_exts:
        return make_template_txt(template_path, replace_dict)
    elif template_ext in html_exts:
        return make_template_html(template_path, root_template_path, replace_dict, layout = layout, max_keys = max_keys)
    elif template_ext in csv_exts:
        return make_template_csv(template_path, replace_dict)
    elif template_ext in excel_exts:
        return make_template_xlsx(template_path, root_template_path, replace_dict)
    elif template_ext in word_exts:
        return make_template_docx(template_path, replace_dict)
    else:
        print(f"Unsupported template file extension: {template_ext}")
        return False

def make_output_txt(template_file, replace_dict, output_file, append):
    file_exists = os.path.exists(output_file)
    open_mode = 'a' if append and file_exists else 'w'

    with open(template_file, 'r', encoding='utf-8') as f:
        template_content = f.read()

    rendered_template = replace_template(template_content, replace_dict, force_replace = True)

    with open(output_file, open_mode, encoding='utf-8') as f:
        if append and file_exists:
            f.write("\n\n\n\n\n")
        f.write(rendered_template)

    print(f"Saved replaced output to [{output_file}]")

def make_output_html_hstack(template_file, replace_dict, output_file, append = False):
    """
    Jinja2テンプレートをレンダリングし、HTMLファイルとして出力します。
    append=Trueの場合、既存のHTMLファイルのテーブルに新しい列を追加します。

    Args:
        template_file (str): Jinja2テンプレートファイルのパス。
        replace_dict (dict): テンプレート内で置き換える値を含む辞書。
        output_file (str): 出力するHTMLファイルのパス。
        append (bool): Trueの場合、既存のファイルに新しいテーブル列を追加します。
                       Falseの場合、ファイルを上書きします。
    """
    file_exists = os.path.exists(output_file)

    # Jinja2環境をセットアップ (テンプレートは初期HTML生成にのみ使用)
    template_dir = os.path.dirname(template_file) if os.path.dirname(template_file) else '.'
    env = Environment(
        loader=FileSystemLoader(template_dir),
        autoescape=select_autoescape(['html', 'xml'])
    )

    if not append or not file_exists:
        # 新規作成の場合
        # テンプレートをロードし、BeautifulSoupでパース
        template = env.get_template(os.path.basename(template_file))
#        rendered_html = template.render(replace_dict)
        rendered_html = render_template(template, replace_dict)

        with open(output_file, 'w', encoding='utf-8') as f:
            f.write(rendered_html)

        print(f"レンダリングされたコンテンツが [{output_file}] に保存されました。")

    else:
        # 既存ファイルに列を追加する場合
        try:
            with open(output_file, 'r', encoding='utf-8') as f:
                existing_html = f.read()
            soup = BeautifulSoup(existing_html, 'html.parser')

            # ヘッダーに新しい列名を追加
            header_row = soup.find('thead').find('tr')
            # 既存の列数を数えて新しい列名を決定 (例: Value 2, Value 3...)
            num_existing_cols = len(header_row.find_all('th'))
            new_column_header = replace_dict.get('date', f'Value {num_existing_cols}')
            new_th = soup.new_tag("th")
            new_th.string = new_column_header
            header_row.append(new_th)

            # 各行に新しいデータセルを追加
            tbody = soup.find('tbody')
            for tr in tbody.find_all('tr'):
                key_td = tr.find('td') # 最初のtdがKey
                if key_td:
                    key = key_td.string # キー名を取得
                    val = replace_dict.get(key) # replace_dictから対応する値を取得

                    new_td = soup.new_tag("td")
                    if is_array_like(val):
                        new_td.string = ", ".join(map(str, val))
                    else:
                        new_td.string = str(val) if val is not None else ""
                    tr.append(new_td)
                else:
                    # キーの<td>が見つからない行の場合、空の<td>を追加
                    new_td = soup.new_tag("td")
                    new_td.string = ""
                    tr.append(new_td)

            # 更新されたHTMLをファイルに書き戻す
            with open(output_file, 'w', encoding='utf-8') as f:
                f.write(str(soup))
            print(f"レンダリングされたコンテンツが [{output_file}] の既存テーブルに新しい列として追加されました。")

        except Exception as e:
            print(f"既存ファイルへの列追加中にエラーが発生しました: {e}")
            # エラーが発生した場合、処理を中断
            pass

def split_html_for_jinja(html_content):
    """
    HTML文字列をthead、tbody、その他の部分に分割します。

    Args:
        html_content (str): 分割したいHTML文字列。

    Returns:
        tuple: (
            thead_before_html,  # <thead>タグより前の部分
            thead_content,      # <thead>...</thead>の部分
            tbody_content,      # <tbody>...</tbody>の部分
            tbody_after_html    # </tbody>タグより後の部分
        )
    """
    
    print("html_content:", html_content)
    
    # <thead>タグとその内容を抽出
    thead_pattern = re.compile(r'thead', re.IGNORECASE | re.DOTALL)
#    thead_pattern = re.compile(r'(<thead\b.*?</thead>)', re.IGNORECASE | re.DOTALL)
    thead_match = thead_pattern.search(html_content)
    if not thead_match:
        raise ValueError("<thead>タグが見つかりません。")

    # <tbody>タグとその内容を抽出
    tbody_pattern = re.compile(r'(<tbody\b.*?</tbody>)', re.IGNORECASE | re.DOTALL)
    tbody_match = tbody_pattern.search(html_content)
    if not tbody_match:
        raise ValueError("<tbody>タグが見つかりません。")

    thead_start_index = thead_match.start()
    thead_end_index = thead_match.end()
    tbody_start_index = tbody_match.start()
    tbody_end_index = tbody_match.end()

    # 各部分を抽出
    thead_before_html = html_content[:thead_start_index]
    thead_content = thead_match.group(1)
    tbody_content = tbody_match.group(1)
    tbody_after_html = html_content[tbody_end_index:]

    return thead_before_html, thead_content, tbody_content, tbody_after_html

def make_output_html_vstack(template_file, replace_dict, output_file, append=False):
    """
    Jinja2テンプレートをレンダリングし、HTMLファイルとして出力します。
    append=Trueの場合、既存のHTMLファイルのテーブルに新しい行を追加します。

    Args:
        template_file (str): Jinja2テンプレートファイルのパス。
        replace_dict (dict): テンプレート内で置き換える値を含む辞書。
                             append=Trueの場合、追加する新しい行のデータを含む辞書。
                             キーはテーブルのヘッダー名に対応し、値はその列のセルデータです。
        output_file (str): 出力するHTMLファイルのパス。
        append (bool): Trueの場合、既存のファイルに新しいテーブル行を追加します。
                       Falseの場合、ファイルを上書きします。
    """

    file_exists = os.path.exists(output_file)

    # Jinja2環境をセットアップ (テンプレートは初期HTML生成にのみ使用)
    template_dir = os.path.dirname(template_file) if os.path.dirname(template_file) else '.'
    env = Environment(
        loader=FileSystemLoader(template_dir),
        autoescape=select_autoescape(['html', 'xml'])
    )

    if not append or not file_exists:
        # 新規作成の場合、または上書きの場合
        template = env.get_template(os.path.basename(template_file))
        rendered_html = render_template(template, replace_dict)

        with open(output_file, 'w', encoding='utf-8') as f:
            f.write(rendered_html)

        print(f"レンダリングされたコンテンツが [{output_file}] に保存されました。")
        return True

    else:
        # 既存ファイルに行を追加する場合
        try:
            # まずUTF-8で読み込みを試みる
            try:
                with open(output_file, 'r', encoding='utf-8') as f:
                    existing_html = f.read()
            except UnicodeDecodeError:
                # UTF-8でデコードできなかった場合、Shift-JISで再試行
                print(f"警告: '{output_file}' をUTF-8で読み込めませんでした。Shift-JISで再試行します。")
                try:
                    with open(output_file, 'r', encoding='shift_jis') as f:
                        existing_html = f.read()
                except UnicodeDecodeError:
                    # Shift-JISでもデコードできなかった場合
                    print(f"Error in make_output_html_vstack(): '{output_file}' をUTF-8およびShift-JISでデコードできませんでした。ファイルが破損しているか、不明なエンコーディングで保存されています。")
                    raise # 元のエラーを再スローして、ユーザーに問題があることを伝える
        except: 
            print(f"Error in make_output_html_vstack(): Could not read [{output_file}]")
            raise

        """
        thead_before_html, thead_content, tbody_content, tbody_after_html = split_html_for_jinja(existing_html)
        print("thead_before_html=", thead_before_html)
        print("thead_content=", thead_content)
        print("tbody_content=", tbody_content)
        print("tbody_after_html=", tbody_after_html)
        exit()
        """

        template = env.get_template(os.path.basename(template_file))
        rendered_html = render_template(template, replace_dict)
        soup_new = BeautifulSoup(rendered_html, 'html.parser')

        soup_existing = BeautifulSoup(existing_html, 'html.parser')

        existing_table = soup_existing.find("table")
        if not existing_table:
            raise ValueError("既存HTMLに<table>が存在しません。")

        # 新しいHTMLから<tbody>タグを取得
        new_tbody = soup_new.find("tbody")
        if not new_tbody:
            raise ValueError("新しいHTMLに<tbody>が存在しません。")

        # 既存<table>の<tbody>タグを取得または作成
        existing_tbody = existing_table.find("tbody")
        if not existing_tbody:
            existing_tbody = soup_existing.new_tag("tbody")
            existing_table.append(existing_tbody)

        # 新しい<tbody>内の行を既存の<tbody>に追加
        for new_row in new_tbody.find_all("tr"):
            existing_tbody.append(new_row)

        # 更新されたHTMLをファイルに書き戻す
        with open(output_file, 'w', encoding='utf-8') as f:
            f.write(str(soup_existing))
        print(f"レンダリングされたコンテンツが [{output_file}] の既存テーブルに新しい行として追加されました。")
        return True


def make_output_html_page(template_file, replace_dict, output_file, append=False):
    """
    Jinja2テンプレートをレンダリングし、HTMLファイルとして出力します。
    append=Trueの場合、既存のHTMLファイルの<body>に<hr>を挿入した後、
    レンダリングした<body>を追加します。

    Args:
        template_file (str): Jinja2テンプレートファイルのパス。
        replace_dict (dict): テンプレート内で置き換える値を含む辞書。
                             append=Trueの場合、追加する新しい行のデータを含む辞書。
                             キーはテーブルのヘッダー名に対応し、値はその列のセルデータです。
        output_file (str): 出力するHTMLファイルのパス。
        append (bool): Trueの場合、既存のファイルに新しいテーブル行を追加します。
                       Falseの場合、ファイルを上書きします。
    """
    file_exists = os.path.exists(output_file)

    # Jinja2環境をセットアップ (テンプレートは初期HTML生成にのみ使用)
    template_dir = os.path.dirname(template_file) if os.path.dirname(template_file) else '.'
    env = Environment(
        loader=FileSystemLoader(template_dir),
        autoescape=select_autoescape(['html', 'xml'])
    )
    template = env.get_template(os.path.basename(template_file))
    rendered_html = render_template(template, replace_dict)

    if not append or not file_exists:
        with open(output_file, 'w', encoding='utf-8') as f:
            f.write(rendered_html)

        print(f"レンダリングされたコンテンツが [{output_file}] に保存されました。")

    else:
        # 既存ファイルに行を追加する場合
        try:
            # まずUTF-8で読み込みを試みる
            try:
                with open(output_file, 'r', encoding='utf-8') as f:
                    existing_html = f.read()
            except UnicodeDecodeError:
                # UTF-8でデコードできなかった場合、Shift-JISで再試行
                print(f"警告: '{output_file}' をUTF-8で読み込めませんでした。Shift-JISで再試行します。")
                try:
                    with open(output_file, 'r', encoding='shift_jis') as f:
                        existing_html = f.read()
                except UnicodeDecodeError:
                    # Shift-JISでもデコードできなかった場合
                    print(f"エラー: '{output_file}' をUTF-8およびShift-JISでデコードできませんでした。ファイルが破損しているか、不明なエンコーディングで保存されています。")
                    raise # 元のエラーを再スローして、ユーザーに問題があることを伝える
            
            base_soup = BeautifulSoup(existing_html, 'html.parser')
# 埋め込み先の<body>を取得
            base_body = base_soup.find('body')

# 埋め込みたいHTMLの<body>の中身を取得
            embed_soup = BeautifulSoup(rendered_html, 'html.parser')
            embed_body_content = embed_soup.find('body').contents

# 元の<body>に内容を追加
            new_hr = base_soup.new_tag("hr")
            base_body.append(new_hr)
            new_hr.insert_after("\n")
            for content in embed_body_content:
                base_body.append(content)

# 更新されたHTMLをファイルに書き戻す
            with open(output_file, 'w', encoding='utf-8') as f:
#                f.write(str(base_soup))
                f.write(base_soup.prettify())
            print(f"レンダリングされたコンテンツが [{output_file}] の既存テーブルに新しい行として追加されました。")

        except Exception as e:
            # UnicodeDecodeError以外のエラーもここで捕捉
            print(f"既存ファイルへの行追加中にエラーが発生しました: {e}")
            pass

def make_output_html(template_file, replace_dict, output_file, layout = "", append = False):
    if layout == "vstack":
        return make_output_html_vstack(template_file, replace_dict, output_file, append = append)
    if layout == "page":
        return make_output_html_page(template_file, replace_dict, output_file, append = append)
    return make_output_html_hstack(template_file, replace_dict, output_file, append = append)

def make_output_csv(template_file, replace_dict, output_file, append):
    try:
        with open(template_file, 'r', encoding='utf-8') as f:
            reader = csv.reader(f)
            header = next(reader)
            template_row_list = next(reader)
            template_content = ",".join(template_row_list)
        
        rendered_template = replace_template(template_content, replace_dict, force_replace=True)
        rendered_rows = rendered_template.splitlines()

        file_exists = os.path.exists(output_file)
        open_mode = 'a' if append and file_exists else 'w'
        
        with open(output_file, open_mode, newline='', encoding='utf-8') as f:
            writer = csv.writer(f)
            if not append or not file_exists:
                writer.writerow(header)
            
            for row_str in rendered_rows:
                row_items = list(csv.reader(io.StringIO(row_str)))[0]
                writer.writerow(row_items)

        print(f"Saved replaced output to [{output_file}]")
    except StopIteration:
        print(f"Error: The template file [{template_file}] is not a valid CSV template (missing header or template row).")
    except Exception as e:
        print(f"Error making CSV output: {e}")


def make_output_xlsx(template_file, replace_dict, output_file, append):
    if openpyxl is None:
        print("openpyxl is not installed. Please install it with 'pip install openpyxl'.")
        return False

    if not os.path.exists(template_file):
        print(f"Error in make_output_xlsx(): Can not find [{template_file}]")
        return False

    file_type = os.path.splitext(template_file)[1]

    if file_type == ".xlsm":
        wb_in = openpyxl.load_workbook(template_file, keep_vba = True)
    else:
        wb_in = openpyxl.load_workbook(template_file)
    ws_in = wb_in.active
    header = [cell.value for cell in ws_in[1]]
    #各セルの幅と高さをリスト変数heightsと widthsに保存
    heights = [ws_in.row_dimensions[row].height for row in range(1, ws_in.max_row + 1)]
    widths = [ws_in.column_dimensions[openpyxl.utils.get_column_letter(col)].width for col in range(1, ws_in.max_column + 1)]
    template_row = [replace_template(cell.value, replace_dict) for cell in ws_in[2]]
    wb_in.close()

    file_exists = os.path.exists(output_file)
    if append and file_exists:
        try:
            if file_type == ".xlsm":
                wb_out = openpyxl.load_workbook(output_file, keep_vba = True)
            else:
                wb_out = openpyxl.load_workbook(output_file)
            ws_out = wb_out.active
        except Exception as e:
            print(f"Error in make_output_xlsx(): Failed to create output file [{output_file}]: {e}")
            return False
    else:
        if file_type == ".xlsm":
            wb_out = openpyxl.load_workbook(template_file, keep_vba = True)
            ws_out = wb_out.active
            ws_out.delete_rows(2, ws_out.max_row)
        else:
            wb_out = openpyxl.Workbook()
            ws_out = wb_out.active
            ws_out.append(header)
        #セルの幅をwidthsに設定
            print(f"Setting column widths: {widths[:10]}...")
            for i, width in enumerate(widths):
                if width is not None:
                    ws_out.column_dimensions[openpyxl.utils.get_column_letter(i + 1)].width = width

    ws_out.append(template_row)
    #追加した行の高さをheights[1]に設定
    print(f"Setting row height: {heights[1]}")
    if heights[1] is not None:
        ws_out.row_dimensions[ws_out.max_row].height = heights[1]
    wb_out.save(output_file)
    print(f"Saved replaced output to [{output_file}]")
    return True

def make_output_docx(template_file, replace_dict, output_file, append):
    if docx is None:
        print("python-docx is not installed. Please install it with 'pip install python-docx'.")
        return False
        
    try:
        file_exists = os.path.exists(output_file)
        if append and file_exists:
            doc_out = docx.Document(output_file)
            doc_out.add_page_break()
        else:
            doc_out = docx.Document()
        
        doc_in = docx.Document(template_file)
        for para in doc_in.paragraphs:
            template = Template(para.text)
#            rendered_text = template.render(replace_dict)
            rendered_text = render_template(template, replace_dict)
            doc_out.add_paragraph(rendered_text)

        for table in doc_in.tables:
            new_table = doc_out.add_table(rows=len(table.rows), cols=len(table.columns))
            for i, row in enumerate(table.rows):
                for j, cell in enumerate(row.cells):
                    template = Template(cell.text)
#                    rendered_text = template.render(replace_dict)
                    rendered_text = render_template(template, replace_dict)
                    new_table.cell(i, j).text = rendered_text

        doc_out.save(output_file)
        print(f"Saved replaced output to [{output_file}]")
        return True
    except Exception as e:
        print(f"Error making DOCX output: {e}")
        return False


def make_output(template_file, replace_dict, output_file, layout = '', append = False):
    template_ext = os.path.splitext(template_file)[1]
    
    output_path = Path(output_file)
    final_output_path = output_path.parent / (output_path.stem + template_ext)

    if template_ext in text_exts:
        make_output_txt(template_file, replace_dict, final_output_path, append = append)
    elif template_ext in html_exts:
        make_output_html(template_file, replace_dict, final_output_path, layout = layout, append = append)
    elif template_ext in csv_exts:
        make_output_csv(template_file, replace_dict, final_output_path, append = append)
    elif template_ext in excel_exts:
        make_output_xlsx(template_file, replace_dict, final_output_path, append = append)
    elif template_ext in word_exts:
        make_output_docx(template_file, replace_dict, final_output_path, append = append)
    else:
        print(f"Unsupported template file extension: {template_ext}")
        return False


def main():
    parser = initialize()
    args = parser.parse_args()

    if args.mode == 'make':
        if not os.path.exists(args.template_file):
            filename = os.path.basename(args.template_file)
            script_dir = os.path.dirname(os.path.abspath(__file__))
            args.template_file = os.path.join(script_dir, "templates", filename)
        if not os.path.exists(args.template_file):
            filename = os.path.basename(args.template_file)
            script_dir = os.path.dirname(os.path.abspath(__file__))
            args.template_file = os.path.join(script_dir, filename)

    template_ext = os.path.splitext(args.template_file)[1]
    if template_ext == ".xlsm" and args.root_template_file == "":
        args.root_template_file = DEFAULT_TEMPLATE_XLSM_FILE
    elif template_ext == ".html" and args.root_template_file == "":
        args.root_template_file = DEFAULT_TEMPLATE_HTML_VSTACK_FILE

    if args.mode == 'make_template':
        if not os.path.exists(args.root_template_file):
            filename = os.path.basename(args.root_template_file)
            script_dir = os.path.dirname(os.path.abspath(__file__))
            args.root_template_file = os.path.join(script_dir, "templates", filename)
        if not os.path.exists(args.root_template_file):
            filename = os.path.basename(args.root_template_file)
            script_dir = os.path.dirname(os.path.abspath(__file__))
            args.root_template_file = os.path.join(script_dir, filename)

    print()
    print(f"Mode: {args.mode}")
    print(f"Base directory    : {args.base_dir}")
    print(f"  sub directories : ", args.subdirs)
    print(f"Root template file: {args.root_template_file}")
    print(f"Template file     : {args.template_file}")
    print(f"  template extension: {template_ext}")
    print(f"  prefix for keys : {args.prefix}")
    print(f"  layout          : {args.layout}")
    print(f"  append          : {args.append}")
    print(f"  max_keys        : {args.max_keys}")
    print(f"Output file: {args.output_file}")
    
    if args.mode == "make" and not os.path.isfile(args.template_file):
        print(f"\nError in main(): Template file [{args.template_file}] is not found.\n")
        exit()

    replace_dict = read_inf_all(args.base_dir, args.subdirs, args, print_level = 1)

    print()
    if args.mode == 'list':
        show_list(replace_dict)
    elif args.mode == 'make_template':
        make_template(args.template_file, args.root_template_file, replace_dict, layout = args.layout, confirm_overwrite = True, max_keys = args.max_keys)
    elif args.mode == 'make':
        make_output(args.template_file, replace_dict, args.output_file, layout = args.layout, append = args.append)


if __name__ == "__main__":
    main()