import os, json, sys, re
from java.util.concurrent.locks import ReentrantLock

# Global cache + metadata
global_project_page_ids = {}
_gpp_last_mtime = 0.0
_gpp_lock = ReentrantLock()

def _detailed_views_basepath():
    # Gateway-safe absolute path to Perspective views
    # (Assumes gateway scope; adjust if running in Designer-only scripts)
    return os.getcwd().replace('\\','/') + '/data/projects/' + system.util.getProjectName() + '/com.inductiveautomation.perspective/views/Detailed-Views'

def _folder_mtime(path):
    """Return the newest mtime under path (views + their view.json files)."""
    newest = 0.0
    try:
        for name in os.listdir(path):
            p = os.path.join(path, name)
            if os.path.isdir(p):
                vjson = os.path.join(p, 'view.json')
                if os.path.exists(vjson):
                    newest = max(newest, os.path.getmtime(vjson))
                newest = max(newest, os.path.getmtime(p))
        newest = max(newest, os.path.getmtime(path))
    except Exception:
        pass
    return newest

def get_project_config(force=False):
    """
    Build/refresh the cache that maps source_id -> page_id.
    Rebuilds when 'Detailed-Views' contents change or when force=True.
    """
    global global_project_page_ids, _gpp_last_mtime
    basePath = _detailed_views_basepath()

    try:
        current_mtime = _folder_mtime(basePath)
    except Exception:
        whid = system.tag.readBlocking(["Configuration/FC"])[0].value
        logger = system.util.getLogger("%s-get_project_config" % whid)
        exc_type, exc_obj, tb = sys.exc_info()
        logger.error("Cannot read Detailed-Views: line %s, %s, %s" % (tb.tb_lineno, exc_type, exc_obj))
        return

    # Only one thread at a time
    _gpp_lock.lock()
    try:
        needs_rebuild = (force or not global_project_page_ids or current_mtime != _gpp_last_mtime)
        if not needs_rebuild:
            return

        mapping = {}
        for entry in os.listdir(basePath):
            view_dir = os.path.join(basePath, entry)
            vjson = os.path.join(view_dir, "view.json")
            if not os.path.isdir(view_dir) or not os.path.exists(vjson):
                continue
            with open(vjson, 'r') as f:
                obj = json.load(f)

            # Walk root children and collect tagProps[0] like before
            for child in obj.get('root', {}).get('children', []):
                tag_props = child.get("props", {}).get("params", {}).get("tagProps")
                if tag_props:
                    source_id = tag_props[0]
                    mapping[source_id] = entry  # page_id

        global_project_page_ids.clear()
        global_project_page_ids.update(mapping)
        _gpp_last_mtime = current_mtime

    finally:
        _gpp_lock.unlock()


def open_pop_up(message):
    error_message = message 
    system.perspective.openPopup("ErrorPopUP", "PopUp-Views/Error", 
                                params ={"Error_message":error_message}, 
                                showCloseIcon = False, modal = True,
                                viewportBound = True,
                                draggable = False,
                                overlayDismiss = True
                                )


def navigate_to_url(self, source_id, page_id):
    url_to_navigate = "/DetailedView/%s/%s" % (page_id, page_id)
    navigation.amzl_navigation.set_session_variables(self, source_id, False)
    system.perspective.navigate(page = url_to_navigate)

def reset_highlights(self):
    """
    Clears highlights, closes docks and popups, and resets session variables.
    """
    # 1. Reset session variables
    self.session.custom.searchId = ""
    self.session.custom.deviceSearchId = ""

    # 2. Close popup and docks
    system.perspective.closePopup(id="TagSearch")
    for dock_id in ["Docked-East-VFD", "Docked-East-MCM", "Docked-East-SNP"]:
        system.perspective.closeDock(dock_id)

    # 3. Trigger CLEAR state via navigate to same view
    current_page = self.page.props.primaryView
    system.perspective.navigate(
        view=current_page,
        params={"highlightTagPath": "CLEAR"}
    )

def source_id_lookup(self, source_id):
    if not source_id:
        return

    # Ensure cache is fresh right before using it
    get_project_config()

    page_id = global_project_page_ids.get(source_id)
    found = False
    final_source_id = source_id

    if not page_id:
        items = source_id.split("/")
        while len(items) > 1:
            items.pop()
            candidate_id = "/".join(items)
            page_id = global_project_page_ids.get(candidate_id)
            if page_id:
                found = True
                final_source_id = candidate_id
                break
    else:
        found = True

    if not found or not page_id:
        open_pop_up("No page id found")
        return

    parts = final_source_id.split("/")
    device = parts[-1]

    if "MCM" in device:
        pathToDevice = "/".join(parts[:3])
    else:
        idx = final_source_id.find(device)
        pathToDevice = final_source_id[:idx + len(device)] if idx != -1 else final_source_id

    highlight_path = pathToDevice + "||High"

    page_path = page_id if page_id.startswith("Detailed-Views/") else "Detailed-Views/" + page_id

    system.perspective.navigate(
        view=page_path,
        params={"highlightTagPath": highlight_path}
    )

def get_child_scada_projects():
    """
    Returns alphabetically sorted child SCADA projects that match '{WHID}_SCADA'
    """
    # Likely intended: (AAA9|KAAA)_SCADA
    pattern = r'(?:[A-Z]{3}[0-9]|K[A-Z]{3})_SCADA'
    all_projects = system.project.getProjectNames()
    return sorted([x for x in all_projects if re.fullmatch(pattern, x)])
 